From 58b90e72f35a6b004230b3197ca624ea70c5a5dd Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 13:20:27 +0530 Subject: [PATCH 01/75] feat: duration control --- frappe/core/doctype/docfield/docfield.json | 4 +- .../doctype/custom_field/custom_field.json | 4 +- .../customize_form_field.json | 4 +- frappe/database/mariadb/database.py | 3 +- frappe/database/postgres/database.py | 3 +- frappe/model/__init__.py | 3 +- frappe/public/build.json | 4 +- .../public/js/frappe/form/controls/control.js | 1 + .../js/frappe/form/controls/duration.js | 159 ++++++++++++++++++ frappe/public/less/controls.less | 63 +++++++ .../website_theme/standard/standard.json | 2 +- 11 files changed, 239 insertions(+), 11 deletions(-) create mode 100644 frappe/public/js/frappe/form/controls/duration.js diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 6d8ee41a5a..04f31375d6 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -86,7 +86,7 @@ "label": "Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", "reqd": 1, "search_index": 1 }, @@ -453,7 +453,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-19 21:54:13.783908", + "modified": "2020-04-30 09:00:38.012601", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 394f38b56c..6534bfd90e 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -116,7 +116,7 @@ "label": "Field Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", "reqd": 1 }, { @@ -383,7 +383,7 @@ "icon": "fa fa-glass", "idx": 1, "links": [], - "modified": "2020-04-10 11:57:10.392218", + "modified": "2020-04-30 09:15:24.394782", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index d7887cf8bd..d1510e0858 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -78,7 +78,7 @@ "label": "Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime", "reqd": 1, "search_index": 1 }, @@ -393,7 +393,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-10 11:58:44.573537", + "modified": "2020-04-30 09:15:51.094586", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index cd053569f0..7350e0aaef 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -55,7 +55,8 @@ class MariaDBDatabase(Database): 'Signature': ('longtext', ''), 'Color': ('varchar', self.VARCHAR_LEN), 'Barcode': ('longtext', ''), - 'Geolocation': ('longtext', '') + 'Geolocation': ('longtext', ''), + 'Duration': ('bigint', '20') } def get_connection(self): diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index e30ef3293f..78dc6d42ec 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -60,7 +60,8 @@ class PostgresDatabase(Database): 'Signature': ('text', ''), 'Color': ('varchar', self.VARCHAR_LEN), 'Barcode': ('text', ''), - 'Geolocation': ('text', '') + 'Geolocation': ('text', ''), + 'Duration': ('bigint', '20') } def get_connection(self): diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index 93ef78df7b..3c5d996439 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -34,7 +34,8 @@ data_fieldtypes = ( 'Signature', 'Color', 'Barcode', - 'Geolocation' + 'Geolocation', + 'Duration' ) no_value_fields = ('Section Break', 'Column Break', 'HTML', 'Table', 'Table MultiSelect', 'Button', 'Image', diff --git a/frappe/public/build.json b/frappe/public/build.json index 7f55924a6b..fe89e3bcc9 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -69,7 +69,8 @@ "node_modules/air-datepicker/dist/js/i18n/datepicker.sk.js", "node_modules/air-datepicker/dist/js/i18n/datepicker.zh.js", "public/js/frappe/ui/capture.js", - "public/js/frappe/form/controls/control.js" + "public/js/frappe/form/controls/control.js", + "public/js/frappe/form/controls/duration.js" ], "js/dialog.min.js": [ "public/js/frappe/dom.js", @@ -259,6 +260,7 @@ "public/js/frappe/form/templates/timeline.html", "public/js/frappe/form/templates/timeline_item.html", "public/js/frappe/form/controls/control.js", + "public/js/frappe/form/controls/duration.js", "public/js/frappe/views/formview.js", "public/js/frappe/form/form.js", "public/js/frappe/meta_tag.js" diff --git a/frappe/public/js/frappe/form/controls/control.js b/frappe/public/js/frappe/form/controls/control.js index 2bf6292abc..168da2717c 100644 --- a/frappe/public/js/frappe/form/controls/control.js +++ b/frappe/public/js/frappe/form/controls/control.js @@ -38,6 +38,7 @@ import './table_multiselect'; import './multiselect_pills'; import './multiselect_list'; import './rating'; +import './duration'; frappe.ui.form.make_control = function (opts) { var control_class_name = "Control" + opts.df.fieldtype.replace(/ /g, ""); diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js new file mode 100644 index 0000000000..8c9aa68b5b --- /dev/null +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -0,0 +1,159 @@ +frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ + make_input: function() { + this._super(); + this.make_picker(); + }, + + make_picker: function() { + this.inputs = []; + this.$picker = $( + `
+
+
` + ); + this.$wrapper.append(this.$picker); + this.build_numeric_input('days', false); + this.build_numeric_input('hrs', false, 23); + this.build_numeric_input('mins', false, 59); + this.build_numeric_input('secs', false, 59); + this.set_duration_picker(); + this.$picker.hide(); + this.bind_events(); + this.refresh(); + }, + + build_numeric_input: function(label, hidden, max) { + let $duration_input = $(` + + `) + + let $input = $(`
`).prepend($duration_input); + + if (max) { + $duration_input.attr('max', max); + } + + this.inputs[label] = $duration_input; + + let $control = $(` +
+
${label}
+
` + ) + + if (hidden) { + $control.addClass('hidden'); + } + $control.prepend($input); + $control.appendTo($('.picker-row')); + }, + + set_duration_picker() { + let total_duration = this.seconds_to_duration(this.value); + if (total_duration.days()) { + this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days()); + } + if (total_duration.hours()) { + this.$picker.find(`[data-duration='hrs']`).prop('value', total_duration.hours()); + } + if (total_duration.minutes()) { + this.$picker.find(`[data-duration='mins']`).prop('value', total_duration.minutes()); + } + if (total_duration.seconds()) { + this.$picker.find(`[data-duration='secs']`).prop('value', total_duration.seconds()); + } + }, + + bind_events: function() { + let me = this; + let clicked = false; + + this.$picker.on('change', '.duration-input', () => { + clicked = false; + me.set_value(me.duration_to_seconds()); + me.set_focus(); + }); + + this.$wrapper.find(".duration-input").mousedown(() => { + clicked = true; + }); + + this.$input.on("focus", () => { + this.$picker.show(); + }); + + this.$input.on("blur", () => { + if (clicked) { + clicked = false; + } else { + this.$picker.hide(); + } + }); + }, + + refresh_input: function() { + this._super(); + this.set_duration_picker(); + }, + + format_for_input: function(value) { + let input_string = ''; + if (value) { + let total_duration = this.seconds_to_duration(value); + + if (total_duration.days()) { + input_string += total_duration.days() + 'd'; + } + if (total_duration.hours()) { + input_string += (input_string.length ? " " : ""); + input_string += total_duration.hours() + 'h'; + } + if (total_duration.minutes()) { + input_string += (input_string.length ? " " : ""); + input_string += total_duration.minutes() + 'm'; + } + if (total_duration.seconds()) { + input_string += (input_string.length ? " " : ""); + input_string += total_duration.seconds() + 's'; + } + } + return input_string; + }, + + seconds_to_duration(value) { + let secs = value; + let total_duration = moment.duration({ + days: Math.floor(secs / (3600 * 24)), + hours: Math.floor(secs % (3600 * 24) / 3600), + minutes: Math.floor(secs % 3600 / 60), + seconds : Math.floor(secs % 60) + }); + return total_duration; + }, + + duration_to_seconds() { + let value = 0; + if (this.inputs) { + let total_duration = moment.duration({ + seconds : parseInt(this.inputs.secs.val()), + minutes : parseInt(this.inputs.mins.val()), + hours : parseInt(this.inputs.hrs.val()), + days : parseInt(this.inputs.days.val()) + }); + + if (total_duration.days()) { + value += total_duration.days() * 24 * 60 * 60; + } + if (total_duration.hours()) { + value += total_duration.hours() * 60 * 60; + } + if (total_duration.minutes()) { + value += total_duration.minutes() * 60; + } + if (total_duration.seconds()) { + value += total_duration.seconds(); + } + } + return value; + } +}); \ No newline at end of file diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index d88e6adaec..9b585a58f3 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -165,3 +165,66 @@ top: 8px; } } + +/* duration control */ + +.duration-picker { + position: relative; + z-index: 999; + + border-radius: 4px; + box-shadow: 0 4px 12px rgba(0,0,0,.15); + background: #fff; + border: 1px solid @border-color; + padding-top: 10px; + padding-left: 5px; + position: absolute; + + &:after, + &:before { + border: solid transparent; + content: " "; + height: 0; + width: 0; + pointer-events: none; + position: absolute; + bottom: 100%; + left: 30px; + } + &:after { + border-color: rgba(255, 255, 255, 0); + border-bottom-color: #fff; + border-width: 8px; + margin-left: -8px; + } + &:before { + border-color: rgba(221, 221, 221, 0); + border-bottom-color: @border-color; + border-width: 9px; + margin-left: -9px; + } + + .duration-row { + margin: 7px; + display: flex; + } + + .duration-col { + margin-left: 2px; + } + + .duration-input { + width: 60px; + } + + .duration-label { + justify-content: center; + } + + .picker-row { + display: flex; + width: 335px; + margin-left: 5px; + margin-bottom: 3px; + } +} \ No newline at end of file diff --git a/frappe/website/website_theme/standard/standard.json b/frappe/website/website_theme/standard/standard.json index a799f25425..799eee72ab 100644 --- a/frappe/website/website_theme/standard/standard.json +++ b/frappe/website/website_theme/standard/standard.json @@ -9,7 +9,7 @@ "font_properties": "300,600", "font_size": "", "idx": 27, - "modified": "2020-04-21 02:10:31.761219", + "modified": "2020-04-24 10:02:44.993836", "modified_by": "Administrator", "module": "Website", "name": "Standard", From 2cd39a1bc4aa4eff44b482b9d786b224b7ac0923 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 14:36:32 +0530 Subject: [PATCH 02/75] feat: add duration options --- .../js/frappe/form/controls/duration.js | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 8c9aa68b5b..c615dd6eb8 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -6,16 +6,17 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ make_picker: function() { this.inputs = []; + this.set_duration_options(); this.$picker = $( `
` ); this.$wrapper.append(this.$picker); - this.build_numeric_input('days', false); - this.build_numeric_input('hrs', false, 23); - this.build_numeric_input('mins', false, 59); - this.build_numeric_input('secs', false, 59); + this.build_numeric_input('days', !this.duration_options.showDays); + this.build_numeric_input('hrs', false); + this.build_numeric_input('mins', false); + this.build_numeric_input('secs', !this.duration_options.showSeconds); this.set_duration_picker(); this.$picker.hide(); this.bind_events(); @@ -48,6 +49,19 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ $control.appendTo($('.picker-row')); }, + set_duration_options() { + let lang = 'en'; + frappe.boot.user && (lang = frappe.boot.user.language); + + this.duration_options = { + lang: lang, + max: 59, + checkRanges: false, + showSeconds: true, + showDays: true, + }; + }, + set_duration_picker() { let total_duration = this.seconds_to_duration(this.value); if (total_duration.days()) { From 94bfe471a56ea51a11224a68e5f87d0e85c81c15 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 19:12:19 +0530 Subject: [PATCH 03/75] fix: wrong days setting --- .../js/frappe/form/controls/duration.js | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index c615dd6eb8..ade49e2293 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -64,17 +64,17 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ set_duration_picker() { let total_duration = this.seconds_to_duration(this.value); - if (total_duration.days()) { - this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days()); + if (total_duration.days) { + this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); } - if (total_duration.hours()) { - this.$picker.find(`[data-duration='hrs']`).prop('value', total_duration.hours()); + if (total_duration.hours) { + this.$picker.find(`[data-duration='hrs']`).prop('value', total_duration.hours); } - if (total_duration.minutes()) { - this.$picker.find(`[data-duration='mins']`).prop('value', total_duration.minutes()); + if (total_duration.minutes) { + this.$picker.find(`[data-duration='mins']`).prop('value', total_duration.minutes); } - if (total_duration.seconds()) { - this.$picker.find(`[data-duration='secs']`).prop('value', total_duration.seconds()); + if (total_duration.seconds) { + this.$picker.find(`[data-duration='secs']`).prop('value', total_duration.seconds); } }, @@ -115,20 +115,20 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ if (value) { let total_duration = this.seconds_to_duration(value); - if (total_duration.days()) { - input_string += total_duration.days() + 'd'; + if (total_duration.days) { + input_string += total_duration.days + 'd'; } - if (total_duration.hours()) { + if (total_duration.hours) { input_string += (input_string.length ? " " : ""); - input_string += total_duration.hours() + 'h'; + input_string += total_duration.hours + 'h'; } - if (total_duration.minutes()) { + if (total_duration.minutes) { input_string += (input_string.length ? " " : ""); - input_string += total_duration.minutes() + 'm'; + input_string += total_duration.minutes + 'm'; } - if (total_duration.seconds()) { + if (total_duration.seconds) { input_string += (input_string.length ? " " : ""); - input_string += total_duration.seconds() + 's'; + input_string += total_duration.seconds + 's'; } } return input_string; @@ -136,36 +136,36 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ seconds_to_duration(value) { let secs = value; - let total_duration = moment.duration({ + let total_duration = { days: Math.floor(secs / (3600 * 24)), hours: Math.floor(secs % (3600 * 24) / 3600), minutes: Math.floor(secs % 3600 / 60), seconds : Math.floor(secs % 60) - }); + }; return total_duration; }, duration_to_seconds() { let value = 0; if (this.inputs) { - let total_duration = moment.duration({ + let total_duration = { seconds : parseInt(this.inputs.secs.val()), minutes : parseInt(this.inputs.mins.val()), hours : parseInt(this.inputs.hrs.val()), days : parseInt(this.inputs.days.val()) - }); + }; - if (total_duration.days()) { - value += total_duration.days() * 24 * 60 * 60; + if (total_duration.days) { + value += total_duration.days * 24 * 60 * 60; } - if (total_duration.hours()) { - value += total_duration.hours() * 60 * 60; + if (total_duration.hours) { + value += total_duration.hours * 60 * 60; } - if (total_duration.minutes()) { - value += total_duration.minutes() * 60; + if (total_duration.minutes) { + value += total_duration.minutes * 60; } - if (total_duration.seconds()) { - value += total_duration.seconds(); + if (total_duration.seconds) { + value += total_duration.seconds; } } return value; From dc555864dd544c168d38b77c1a814991772bb572 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 21:40:35 +0530 Subject: [PATCH 04/75] fix: add formatter and util methods for duration fieldtype --- .../js/frappe/form/controls/duration.js | 35 ++----------------- frappe/public/js/frappe/form/formatters.js | 7 ++++ frappe/public/js/frappe/utils/utils.js | 34 ++++++++++++++++++ 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index ade49e2293..c0ca37dea3 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -63,7 +63,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, set_duration_picker() { - let total_duration = this.seconds_to_duration(this.value); + let total_duration = frappe.utils.seconds_to_duration(this.value); if (total_duration.days) { this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); } @@ -111,38 +111,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, format_for_input: function(value) { - let input_string = ''; - if (value) { - let total_duration = this.seconds_to_duration(value); - - if (total_duration.days) { - input_string += total_duration.days + 'd'; - } - if (total_duration.hours) { - input_string += (input_string.length ? " " : ""); - input_string += total_duration.hours + 'h'; - } - if (total_duration.minutes) { - input_string += (input_string.length ? " " : ""); - input_string += total_duration.minutes + 'm'; - } - if (total_duration.seconds) { - input_string += (input_string.length ? " " : ""); - input_string += total_duration.seconds + 's'; - } - } - return input_string; - }, - - seconds_to_duration(value) { - let secs = value; - let total_duration = { - days: Math.floor(secs / (3600 * 24)), - hours: Math.floor(secs % (3600 * 24) / 3600), - minutes: Math.floor(secs % 3600 / 60), - seconds : Math.floor(secs % 60) - }; - return total_duration; + return frappe.utils.get_formatted_duration(value); }, duration_to_seconds() { diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index d178c59100..cc24c48a1f 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -188,6 +188,13 @@ frappe.form.formatters = { return value || ""; }, + Duration: function(value) { + if (value) { + value = frappe.utils.get_formatted_duration(value); + } + + return value || ""; + }, LikedBy: function(value) { var html = ""; $.each(JSON.parse(value || "[]"), function(i, v) { diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 1afdbfd81c..2584248711 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -778,6 +778,40 @@ Object.assign(frappe.utils, { version: M[1], }; }, + + get_formatted_duration(value) { + let duration = ''; + if (value) { + let total_duration = frappe.utils.seconds_to_duration(value); + + if (total_duration.days) { + duration += total_duration.days + 'd'; + } + if (total_duration.hours) { + duration += (duration.length ? " " : ""); + duration += total_duration.hours + 'h'; + } + if (total_duration.minutes) { + duration += (duration.length ? " " : ""); + duration += total_duration.minutes + 'm'; + } + if (total_duration.seconds) { + duration += (duration.length ? " " : ""); + duration += total_duration.seconds + 's'; + } + } + return duration; + }, + seconds_to_duration(value) { + let secs = value; + let total_duration = { + days: Math.floor(secs / (3600 * 24)), + hours: Math.floor(secs % (3600 * 24) / 3600), + minutes: Math.floor(secs % 3600 / 60), + seconds : Math.floor(secs % 60) + }; + return total_duration; + }, }); // Array de duplicate From 003225f20c4c776f794cb3935a69c59933675dc1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 21:59:46 +0530 Subject: [PATCH 05/75] fix: filters for duration fieldtype --- frappe/public/js/frappe/form/controls/duration.js | 4 ++++ frappe/public/js/frappe/ui/filters/filters.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index c0ca37dea3..791882185c 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -105,6 +105,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }); }, + get_value() { + return cint(this.value); + }, + refresh_input: function() { this._super(); this.set_duration_picker(); diff --git a/frappe/public/js/frappe/ui/filters/filters.js b/frappe/public/js/frappe/ui/filters/filters.js index 3646dc6b6e..94e2b59d22 100644 --- a/frappe/public/js/frappe/ui/filters/filters.js +++ b/frappe/public/js/frappe/ui/filters/filters.js @@ -200,6 +200,8 @@ frappe.ui.FilterList = Class.extend({ value = {0:"Draft", 1:"Submitted", 2:"Cancelled"}[value] || value; } else if(field.df.original_type==="Check") { value = {0:"No", 1:"Yes"}[cint(value)]; + } else if (field.df.original_type === "Duration") { + value = frappe.utils.get_formatted_duration(value); } value = frappe.format(value, field.df, {only_value: 1}); From 58e4ed06037b7ea45a88a087db0e941b1196d9a5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 5 May 2020 00:59:03 +0530 Subject: [PATCH 06/75] fix: control styling in child table --- frappe/public/js/frappe/form/controls/duration.js | 4 ++-- frappe/public/less/controls.less | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 791882185c..86dee3866f 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -25,7 +25,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ build_numeric_input: function(label, hidden, max) { let $duration_input = $(` - + `) let $input = $(`
`).prepend($duration_input); @@ -46,7 +46,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ $control.addClass('hidden'); } $control.prepend($input); - $control.appendTo($('.picker-row')); + $control.appendTo(this.$picker.find('.picker-row')); }, set_duration_options() { diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index 9b585a58f3..ac2936b149 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -204,6 +204,12 @@ margin-left: -9px; } + .row .col { + // for fixing layout in child table + padding-left: 0px !important; + padding-right: 0px !important; + } + .duration-row { margin: 7px; display: flex; @@ -215,6 +221,7 @@ .duration-input { width: 60px; + border: 1px solid rgba(0, 0, 0, 0.25) !important; } .duration-label { From 87f2c66e0f3b13359b94819017c3252033089a4d Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 00:09:55 +0530 Subject: [PATCH 07/75] feat: options to hide days and seconds for duration control --- .../js/frappe/form/controls/duration.js | 21 ++++++++++--------- frappe/public/js/frappe/utils/utils.js | 10 ++++++--- frappe/public/less/controls.less | 2 +- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 86dee3866f..49c1e46530 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -50,20 +50,16 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, set_duration_options() { - let lang = 'en'; - frappe.boot.user && (lang = frappe.boot.user.language); - this.duration_options = { - lang: lang, - max: 59, - checkRanges: false, showSeconds: true, showDays: true, }; }, set_duration_picker() { - let total_duration = frappe.utils.seconds_to_duration(this.value); + let total_duration = frappe.utils.seconds_to_duration(this.value, + this.duration_options.showDays, this.set_duration_options.showSeconds); + if (total_duration.days) { this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); } @@ -115,18 +111,23 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, format_for_input: function(value) { - return frappe.utils.get_formatted_duration(value); + return frappe.utils.get_formatted_duration(value, + this.duration_options.showDays, this.duration_options.showSeconds); }, duration_to_seconds() { let value = 0; if (this.inputs) { let total_duration = { - seconds : parseInt(this.inputs.secs.val()), minutes : parseInt(this.inputs.mins.val()), hours : parseInt(this.inputs.hrs.val()), - days : parseInt(this.inputs.days.val()) }; + if (this.duration_options.showDays) { + total_duration.days = parseInt(this.inputs.days.val()); + } + if (this.duration_options.showSeconds) { + total_duration.seconds = parseInt(this.inputs.secs.val()); + } if (total_duration.days) { value += total_duration.days * 24 * 60 * 60; diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 2584248711..559598de5b 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -779,10 +779,10 @@ Object.assign(frappe.utils, { }; }, - get_formatted_duration(value) { + get_formatted_duration(value, showDays=true, showSeconds=true) { let duration = ''; if (value) { - let total_duration = frappe.utils.seconds_to_duration(value); + let total_duration = frappe.utils.seconds_to_duration(value, showDays, showSeconds); if (total_duration.days) { duration += total_duration.days + 'd'; @@ -802,7 +802,7 @@ Object.assign(frappe.utils, { } return duration; }, - seconds_to_duration(value) { + seconds_to_duration(value, showDays=true, showSeconds=true) { let secs = value; let total_duration = { days: Math.floor(secs / (3600 * 24)), @@ -810,6 +810,10 @@ Object.assign(frappe.utils, { minutes: Math.floor(secs % 3600 / 60), seconds : Math.floor(secs % 60) }; + if (!showDays) { + total_duration.hours = Math.floor(secs / 3600) + total_duration.days = 0 + } return total_duration; }, }); diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index ac2936b149..b6ac3cf359 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -230,8 +230,8 @@ .picker-row { display: flex; - width: 335px; margin-left: 5px; margin-bottom: 3px; + margin-right: 12px; } } \ No newline at end of file From 156a00724fd6adbeb65471e99d4fb0744c6e7c3f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 09:39:57 +0530 Subject: [PATCH 08/75] feat: add duration options to Duration docfield --- frappe/core/doctype/docfield/docfield.json | 18 +++++++++++++++++- .../public/js/frappe/form/controls/duration.js | 11 +++-------- frappe/public/js/frappe/form/formatters.js | 5 +++-- frappe/public/js/frappe/model/meta.js | 8 ++++++++ frappe/public/js/frappe/utils/utils.js | 8 ++++---- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 04f31375d6..8ae2b740b5 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -13,6 +13,8 @@ "fieldname", "precision", "length", + "show_days", + "show_seconds", "reqd", "search_index", "in_list_view", @@ -448,12 +450,26 @@ { "fieldname": "column_break_38", "fieldtype": "Column Break" + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_days", + "fieldtype": "Check", + "label": "Show Days" + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_seconds", + "fieldtype": "Check", + "label": "Show Seconds" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-30 09:00:38.012601", + "modified": "2020-05-06 09:06:25.224411", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 49c1e46530..de8aa96c95 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -50,15 +50,11 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, set_duration_options() { - this.duration_options = { - showSeconds: true, - showDays: true, - }; + this.duration_options = frappe.meta.get_duration_options(this.df, this.get_doc()); }, set_duration_picker() { - let total_duration = frappe.utils.seconds_to_duration(this.value, - this.duration_options.showDays, this.set_duration_options.showSeconds); + let total_duration = frappe.utils.seconds_to_duration(this.value, this.duration_options); if (total_duration.days) { this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); @@ -111,8 +107,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, format_for_input: function(value) { - return frappe.utils.get_formatted_duration(value, - this.duration_options.showDays, this.duration_options.showSeconds); + return frappe.utils.get_formatted_duration(value, this.duration_options); }, duration_to_seconds() { diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index cc24c48a1f..a9abd1c601 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -188,9 +188,10 @@ frappe.form.formatters = { return value || ""; }, - Duration: function(value) { + Duration: function(value, docfield, doc) { if (value) { - value = frappe.utils.get_formatted_duration(value); + let duration_options = frappe.meta.get_duration_options(docfield, doc); + value = frappe.utils.get_formatted_duration(value, duration_options); } return value || ""; diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index b7ad52838c..acde2a367f 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -267,4 +267,12 @@ $.extend(frappe.meta, { } return precision; }, + + get_duration_options: function(df, doc) { + let duration_options = { + showDays: df.show_days, + showSeconds: df.show_seconds + }; + return duration_options; + } }); diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 559598de5b..9281ba6c43 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -779,10 +779,10 @@ Object.assign(frappe.utils, { }; }, - get_formatted_duration(value, showDays=true, showSeconds=true) { + get_formatted_duration(value, duration_options) { let duration = ''; if (value) { - let total_duration = frappe.utils.seconds_to_duration(value, showDays, showSeconds); + let total_duration = frappe.utils.seconds_to_duration(value, duration_options); if (total_duration.days) { duration += total_duration.days + 'd'; @@ -802,7 +802,7 @@ Object.assign(frappe.utils, { } return duration; }, - seconds_to_duration(value, showDays=true, showSeconds=true) { + seconds_to_duration(value, duration_options) { let secs = value; let total_duration = { days: Math.floor(secs / (3600 * 24)), @@ -810,7 +810,7 @@ Object.assign(frappe.utils, { minutes: Math.floor(secs % 3600 / 60), seconds : Math.floor(secs % 60) }; - if (!showDays) { + if (!duration_options.showDays) { total_duration.hours = Math.floor(secs / 3600) total_duration.days = 0 } From 94116d322784098f2a215ceb134efd1a24e73589 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 09:51:16 +0530 Subject: [PATCH 09/75] fix: duration options for filters --- frappe/public/js/frappe/ui/filters/filters.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/filters/filters.js b/frappe/public/js/frappe/ui/filters/filters.js index 94e2b59d22..9a394eade3 100644 --- a/frappe/public/js/frappe/ui/filters/filters.js +++ b/frappe/public/js/frappe/ui/filters/filters.js @@ -201,7 +201,11 @@ frappe.ui.FilterList = Class.extend({ } else if(field.df.original_type==="Check") { value = {0:"No", 1:"Yes"}[cint(value)]; } else if (field.df.original_type === "Duration") { - value = frappe.utils.get_formatted_duration(value); + let duration_options = { + showDays: field.df.show_days, + showSeconds: field.df.show_seconds + }; + value = frappe.utils.get_formatted_duration(value, duration_options); } value = frappe.format(value, field.df, {only_value: 1}); From b696306edc65bb96a4ed450451c26ca61937b6c9 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 10:15:11 +0530 Subject: [PATCH 10/75] fix: set duration options in refresh_input --- .../js/frappe/form/controls/duration.js | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index de8aa96c95..c177ae6f17 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -13,10 +13,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ ` ); this.$wrapper.append(this.$picker); - this.build_numeric_input('days', !this.duration_options.showDays); - this.build_numeric_input('hrs', false); - this.build_numeric_input('mins', false); - this.build_numeric_input('secs', !this.duration_options.showSeconds); + this.build_numeric_input("days", !this.duration_options.showDays); + this.build_numeric_input("hrs", false); + this.build_numeric_input("mins", false); + this.build_numeric_input("secs", !this.duration_options.showSeconds); this.set_duration_picker(); this.$picker.hide(); this.bind_events(); @@ -31,7 +31,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let $input = $(`
`).prepend($duration_input); if (max) { - $duration_input.attr('max', max); + $duration_input.attr("max", max); } this.inputs[label] = $duration_input; @@ -43,10 +43,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ ) if (hidden) { - $control.addClass('hidden'); + $control.addClass("hidden"); } $control.prepend($input); - $control.appendTo(this.$picker.find('.picker-row')); + $control.appendTo(this.$picker.find(".picker-row")); }, set_duration_options() { @@ -57,16 +57,16 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let total_duration = frappe.utils.seconds_to_duration(this.value, this.duration_options); if (total_duration.days) { - this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); + this.$picker.find(`[data-duration="days"]`).prop("value", total_duration.days); } if (total_duration.hours) { - this.$picker.find(`[data-duration='hrs']`).prop('value', total_duration.hours); + this.$picker.find(`[data-duration="hrs"]`).prop("value", total_duration.hours); } if (total_duration.minutes) { - this.$picker.find(`[data-duration='mins']`).prop('value', total_duration.minutes); + this.$picker.find(`[data-duration="mins"]`).prop("value", total_duration.minutes); } if (total_duration.seconds) { - this.$picker.find(`[data-duration='secs']`).prop('value', total_duration.seconds); + this.$picker.find(`[data-duration="secs"]`).prop("value", total_duration.seconds); } }, @@ -74,7 +74,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let me = this; let clicked = false; - this.$picker.on('change', '.duration-input', () => { + this.$picker.on("change", ".duration-input", () => { clicked = false; me.set_value(me.duration_to_seconds()); me.set_focus(); @@ -103,6 +103,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ refresh_input: function() { this._super(); + this.set_duration_options(); this.set_duration_picker(); }, From 972833d13fb20deebe95acb47d0495baed655831 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 12:36:22 +0530 Subject: [PATCH 11/75] fix: change duration fieldtype to decimal --- frappe/database/mariadb/database.py | 2 +- frappe/database/postgres/database.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index 7350e0aaef..4ec89c126d 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -56,7 +56,7 @@ class MariaDBDatabase(Database): 'Color': ('varchar', self.VARCHAR_LEN), 'Barcode': ('longtext', ''), 'Geolocation': ('longtext', ''), - 'Duration': ('bigint', '20') + 'Duration': ('decimal', '18,6') } def get_connection(self): diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index 78dc6d42ec..e348916705 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -61,7 +61,7 @@ class PostgresDatabase(Database): 'Color': ('varchar', self.VARCHAR_LEN), 'Barcode': ('text', ''), 'Geolocation': ('text', ''), - 'Duration': ('bigint', '20') + 'Duration': ('decimal', '18,6') } def get_connection(self): From 18ce053a7475759f378038bb7017099c6db989a1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 13:39:40 +0530 Subject: [PATCH 12/75] fix: refresh input --- .../js/frappe/form/controls/duration.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index c177ae6f17..c764c9b041 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -56,17 +56,19 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ set_duration_picker() { let total_duration = frappe.utils.seconds_to_duration(this.value, this.duration_options); - if (total_duration.days) { - this.$picker.find(`[data-duration="days"]`).prop("value", total_duration.days); - } - if (total_duration.hours) { - this.$picker.find(`[data-duration="hrs"]`).prop("value", total_duration.hours); - } - if (total_duration.minutes) { - this.$picker.find(`[data-duration="mins"]`).prop("value", total_duration.minutes); - } - if (total_duration.seconds) { - this.$picker.find(`[data-duration="secs"]`).prop("value", total_duration.seconds); + if (this.$picker) { + if (total_duration.days) { + this.$picker.find(`[data-duration="days"]`).prop("value", total_duration.days); + } + if (total_duration.hours) { + this.$picker.find(`[data-duration="hrs"]`).prop("value", total_duration.hours); + } + if (total_duration.minutes) { + this.$picker.find(`[data-duration="mins"]`).prop("value", total_duration.minutes); + } + if (total_duration.seconds) { + this.$picker.find(`[data-duration="secs"]`).prop("value", total_duration.seconds); + } } }, From 74eba09e526e521e503ac10b0597dfe6677cbd56 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 14:06:34 +0530 Subject: [PATCH 13/75] fix(style): remove borders on focus for individual inputs --- frappe/public/less/controls.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index b6ac3cf359..161293b558 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -224,6 +224,10 @@ border: 1px solid rgba(0, 0, 0, 0.25) !important; } + .duration-input:focus { + outline: None; + } + .duration-label { justify-content: center; } From 8383b9fc3ac18485d2379b12a0b2d97f34c96f37 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Wed, 6 May 2020 14:21:44 +0530 Subject: [PATCH 14/75] fix: clear_sessions should by default include mobile whenever password was changed, the system only logged out user for the current device type; which is incorrect. suppose if the user logs in through mobile and selects "logout all devices" on password reset on desktop, the system would log the user out of all desktop instances, leaving the mobile instance active. Signed-off-by: Chinmay D. Pai --- frappe/sessions.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/frappe/sessions.py b/frappe/sessions.py index cca40cbc55..d317d6caf3 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -33,7 +33,7 @@ def clear_sessions(user=None, keep_current=False, device=None, force=False): :param user: user name (default: current user) :param keep_current: keep current session (default: false) - :param device: delete sessions of this device (default: desktop) + :param device: delete sessions of this device (default: desktop, mobile) :param force: triggered by the user (default false) ''' @@ -49,13 +49,16 @@ def get_sessions_to_clear(user=None, keep_current=False, device=None): :param user: user name (default: current user) :param keep_current: keep current session (default: false) - :param device: delete sessions of this device (default: desktop) + :param device: delete sessions of this device (default: desktop, mobile) ''' if not user: user = frappe.session.user if not device: - device = frappe.session.data.device or "desktop" + device = ("desktop", "mobile") + + if not isinstance(device, (tuple, list)): + device = (device,) offset = 0 if user == frappe.session.user: @@ -68,12 +71,12 @@ def get_sessions_to_clear(user=None, keep_current=False, device=None): return frappe.db.sql_list(""" SELECT `sid` FROM `tabSessions` - WHERE user=%s - AND device=%s + WHERE user=%(user)s + AND device in %(device)s {condition} ORDER BY `lastupdate` DESC LIMIT 100 OFFSET {offset}""".format(condition=condition, offset=offset), - (user, device)) + {"user": user, "device": device}) def delete_session(sid=None, user=None, reason="Session Expired"): from frappe.core.doctype.activity_log.feed import logout_feed From 9b4e5c5a14898cf74de581e58340e5ac1edd17fc Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 7 May 2020 15:26:29 +0530 Subject: [PATCH 15/75] fix: do not create contacts if unchecked --- .../core/doctype/communication/communication.py | 3 +++ .../doctype/email_account/email_account.json | 15 +++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index abd24fb468..12a9a8962c 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -337,6 +337,9 @@ def get_permission_query_conditions_for_communication(user): .format(email_accounts=','.join(email_accounts)) def get_contacts(email_strings): + if (not self.email_account) or (self.email_account and not frappe.db.get_value("Email Account", self.email_account, "create_contact")): + return [] + email_addrs = [] for email_string in email_strings: diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json index 6bde0291a0..d664e0f9fb 100644 --- a/frappe/email/doctype/email_account/email_account.json +++ b/frappe/email/doctype/email_account/email_account.json @@ -29,6 +29,7 @@ "default_incoming", "email_sync_option", "initial_sync_count", + "create_contact", "section_break_12", "enable_automatic_linking", "section_break_13", @@ -114,9 +115,9 @@ "depends_on": "eval:!doc.service", "fieldname": "domain", "fieldtype": "Link", - "label": "Domain", "in_list_view": 1, "in_standard_filter": 1, + "label": "Domain", "options": "Email Domain" }, { @@ -408,11 +409,17 @@ "fieldname": "use_ssl_for_outgoing", "fieldtype": "Check", "label": "Use SSL for Outgoing" + }, + { + "default": "0", + "fieldname": "create_contact", + "fieldtype": "Check", + "label": "Create Contacts from Incoming Emails" } ], "icon": "fa fa-inbox", "links": [], - "modified": "2020-04-06 19:20:50.491146", + "modified": "2020-05-07 15:18:43.931499", "modified_by": "Administrator", "module": "Email", "name": "Email Account", @@ -427,8 +434,8 @@ "write": 1 }, { - "read": 1, - "role": "Inbox User" + "read": 1, + "role": "Inbox User" } ], "sort_field": "modified", From 413ff349ea31139931fdcf429f01b86aebfe0387 Mon Sep 17 00:00:00 2001 From: Chinmay Pai Date: Thu, 7 May 2020 18:54:55 +0530 Subject: [PATCH 16/75] chore: get_contacts only if email account is set --- frappe/core/doctype/communication/communication.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 12a9a8962c..07a06701e6 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -258,7 +258,10 @@ class Communication(Document): # Timeline Links def set_timeline_links(self): - contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc]) + if 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]) + else: + contacts = [] for contact_name in contacts: self.add_link('Contact', contact_name) @@ -337,9 +340,6 @@ def get_permission_query_conditions_for_communication(user): .format(email_accounts=','.join(email_accounts)) def get_contacts(email_strings): - if (not self.email_account) or (self.email_account and not frappe.db.get_value("Email Account", self.email_account, "create_contact")): - return [] - email_addrs = [] for email_string in email_strings: From baa0d5d26c81cb798048d34917c66561d3b756ac Mon Sep 17 00:00:00 2001 From: Chinmay Pai Date: Thu, 7 May 2020 19:02:54 +0530 Subject: [PATCH 17/75] chore: simplify contacts condition --- frappe/core/doctype/communication/communication.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 07a06701e6..aecf35fcdf 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -258,10 +258,9 @@ 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"): contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc]) - else: - contacts = [] for contact_name in contacts: self.add_link('Contact', contact_name) From 8761ce978667cdb542719cc24d33c818cb3afcf1 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 12 May 2020 16:48:05 +0530 Subject: [PATCH 18/75] fix: enable create contact by default --- frappe/core/doctype/communication/communication.py | 1 + frappe/email/doctype/email_account/email_account.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index aecf35fcdf..295d2464c8 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -261,6 +261,7 @@ class Communication(Document): contacts = [] if 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]) + for contact_name in contacts: self.add_link('Contact', contact_name) diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json index d664e0f9fb..37ecf4a4e6 100644 --- a/frappe/email/doctype/email_account/email_account.json +++ b/frappe/email/doctype/email_account/email_account.json @@ -411,7 +411,7 @@ "label": "Use SSL for Outgoing" }, { - "default": "0", + "default": "1", "fieldname": "create_contact", "fieldtype": "Check", "label": "Create Contacts from Incoming Emails" From 8a74b4051de54cab4779a1bd5715c1572977c7aa Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 12 May 2020 16:58:11 +0530 Subject: [PATCH 19/75] Update email_account.json --- frappe/email/doctype/email_account/email_account.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json index 37ecf4a4e6..057638697a 100644 --- a/frappe/email/doctype/email_account/email_account.json +++ b/frappe/email/doctype/email_account/email_account.json @@ -419,7 +419,7 @@ ], "icon": "fa fa-inbox", "links": [], - "modified": "2020-05-07 15:18:43.931499", + "modified": "2020-05-11 15:18:43.931499", "modified_by": "Administrator", "module": "Email", "name": "Email Account", @@ -441,4 +441,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From b05d0705d11e68bdde30fe93fd925580b64d7878 Mon Sep 17 00:00:00 2001 From: Neha Sacher Date: Mon, 11 May 2020 18:25:59 +0530 Subject: [PATCH 20/75] fix: fixed the reports print format --- frappe/templates/styles/standard.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/templates/styles/standard.css b/frappe/templates/styles/standard.css index f5eeb1c7fb..b87aa46d23 100644 --- a/frappe/templates/styles/standard.css +++ b/frappe/templates/styles/standard.css @@ -145,6 +145,11 @@ table.no-border, table.no-border td { margin: 3px 0px 3px; } +.print-format table td pre { + white-space: normal; + word-break: normal; +} + table td div { {% if not print_settings.allow_page_break_inside_tables %} /* needed to avoid partial cutting of text between page break in wkhtmltopdf */ From 8d79230be2a4e570b311ee1808a9b6c4518d98a7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 13:33:45 +0530 Subject: [PATCH 21/75] fix: code clean-up --- frappe/public/build.json | 4 +-- .../js/frappe/form/controls/duration.js | 34 +++++++------------ 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/frappe/public/build.json b/frappe/public/build.json index 86f3d23c99..d56907b558 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -72,8 +72,7 @@ "node_modules/air-datepicker/dist/js/i18n/datepicker.sk.js", "node_modules/air-datepicker/dist/js/i18n/datepicker.zh.js", "public/js/frappe/ui/capture.js", - "public/js/frappe/form/controls/control.js", - "public/js/frappe/form/controls/duration.js" + "public/js/frappe/form/controls/control.js" ], "js/dialog.min.js": [ "public/js/frappe/dom.js", @@ -264,7 +263,6 @@ "public/js/frappe/form/templates/timeline.html", "public/js/frappe/form/templates/timeline_item.html", "public/js/frappe/form/controls/control.js", - "public/js/frappe/form/controls/duration.js", "public/js/frappe/views/formview.js", "public/js/frappe/form/form.js", "public/js/frappe/meta_tag.js" diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index c764c9b041..01e18de731 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -14,9 +14,9 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ ); this.$wrapper.append(this.$picker); this.build_numeric_input("days", !this.duration_options.showDays); - this.build_numeric_input("hrs", false); - this.build_numeric_input("mins", false); - this.build_numeric_input("secs", !this.duration_options.showSeconds); + this.build_numeric_input("hours", false); + this.build_numeric_input("minutes", false); + this.build_numeric_input("seconds", !this.duration_options.showSeconds); this.set_duration_picker(); this.$picker.hide(); this.bind_events(); @@ -38,7 +38,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let $control = $(`
-
${label}
+
${__(label)}
` ) @@ -57,29 +57,19 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let total_duration = frappe.utils.seconds_to_duration(this.value, this.duration_options); if (this.$picker) { - if (total_duration.days) { - this.$picker.find(`[data-duration="days"]`).prop("value", total_duration.days); - } - if (total_duration.hours) { - this.$picker.find(`[data-duration="hrs"]`).prop("value", total_duration.hours); - } - if (total_duration.minutes) { - this.$picker.find(`[data-duration="mins"]`).prop("value", total_duration.minutes); - } - if (total_duration.seconds) { - this.$picker.find(`[data-duration="secs"]`).prop("value", total_duration.seconds); - } + Object.keys(total_duration).forEach(duration => { + this.inputs[duration].prop("value", total_duration[duration]); + }); } }, bind_events: function() { - let me = this; let clicked = false; this.$picker.on("change", ".duration-input", () => { clicked = false; - me.set_value(me.duration_to_seconds()); - me.set_focus(); + this.set_value(this.duration_to_seconds()); + this.set_focus(); }); this.$wrapper.find(".duration-input").mousedown(() => { @@ -117,14 +107,14 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let value = 0; if (this.inputs) { let total_duration = { - minutes : parseInt(this.inputs.mins.val()), - hours : parseInt(this.inputs.hrs.val()), + minutes : parseInt(this.inputs.minutes.val()), + hours : parseInt(this.inputs.hours.val()), }; if (this.duration_options.showDays) { total_duration.days = parseInt(this.inputs.days.val()); } if (this.duration_options.showSeconds) { - total_duration.seconds = parseInt(this.inputs.secs.val()); + total_duration.seconds = parseInt(this.inputs.seconds.val()); } if (total_duration.days) { From 776da4cc907e9b3a332d82ea7d1cda2898b2f02f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 14:00:10 +0530 Subject: [PATCH 22/75] feat: add duration field in web form --- frappe/website/doctype/web_form_field/web_form_field.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/web_form_field/web_form_field.json b/frappe/website/doctype/web_form_field/web_form_field.json index 90f9b24a16..ef7b1c4ddf 100644 --- a/frappe/website/doctype/web_form_field/web_form_field.json +++ b/frappe/website/doctype/web_form_field/web_form_field.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2014-09-01 14:14:14.292173", "doctype": "DocType", "editable_grid": 1, @@ -34,7 +35,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Fieldtype", - "options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nInt\nLink\nRating\nSelect\nSmall Text\nText\nText Editor\nTable\nSection Break\nColumn Break" + "options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nDuration\nFloat\nHTML\nInt\nLink\nRating\nSelect\nSmall Text\nText\nText Editor\nTable\nSection Break\nColumn Break" }, { "fieldname": "label", @@ -119,7 +120,8 @@ } ], "istable": 1, - "modified": "2019-06-07 12:17:10.547133", + "links": [], + "modified": "2020-05-13 13:35:08.454427", "modified_by": "Administrator", "module": "Website", "name": "Web Form Field", From 3174d16ab33f0965e91f55cc38944b9f8ce6d6b2 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 18:52:05 +0530 Subject: [PATCH 23/75] fix: sider issues, add comments --- .../js/frappe/form/controls/duration.js | 23 +++++++++++-------- frappe/public/js/frappe/form/formatters.js | 2 +- frappe/public/js/frappe/model/meta.js | 2 +- frappe/public/js/frappe/utils/utils.js | 6 ++--- frappe/public/less/controls.less | 3 +-- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 01e18de731..d6d512da9c 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -26,7 +26,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ build_numeric_input: function(label, hidden, max) { let $duration_input = $(` - `) + `); let $input = $(`
`).prepend($duration_input); @@ -40,7 +40,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({
${__(label)}
` - ) + ); if (hidden) { $control.addClass("hidden"); @@ -50,7 +50,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, set_duration_options() { - this.duration_options = frappe.meta.get_duration_options(this.df, this.get_doc()); + this.duration_options = frappe.meta.get_duration_options(this.df); }, set_duration_picker() { @@ -64,26 +64,31 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, bind_events: function() { + // flag to handle the display property of the picker let clicked = false; + this.$wrapper.find(".duration-input").mousedown(() => { + // input in individual duration boxes + clicked = true; + }); + this.$picker.on("change", ".duration-input", () => { + // duration changed in individual boxes clicked = false; this.set_value(this.duration_to_seconds()); this.set_focus(); }); - this.$wrapper.find(".duration-input").mousedown(() => { - clicked = true; - }); - this.$input.on("focus", () => { this.$picker.show(); }); this.$input.on("blur", () => { + // input in duration boxes, don't close the picker if (clicked) { clicked = false; } else { + // blur event was not due to duration inputs this.$picker.hide(); } }); @@ -107,8 +112,8 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let value = 0; if (this.inputs) { let total_duration = { - minutes : parseInt(this.inputs.minutes.val()), - hours : parseInt(this.inputs.hours.val()), + minutes: parseInt(this.inputs.minutes.val()), + hours: parseInt(this.inputs.hours.val()), }; if (this.duration_options.showDays) { total_duration.days = parseInt(this.inputs.days.val()); diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index a9abd1c601..23c0ad5d5d 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -190,7 +190,7 @@ frappe.form.formatters = { }, Duration: function(value, docfield, doc) { if (value) { - let duration_options = frappe.meta.get_duration_options(docfield, doc); + let duration_options = frappe.meta.get_duration_options(docfield); value = frappe.utils.get_formatted_duration(value, duration_options); } diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index acde2a367f..c6b5ca0b4a 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -268,7 +268,7 @@ $.extend(frappe.meta, { return precision; }, - get_duration_options: function(df, doc) { + get_duration_options: function(df) { let duration_options = { showDays: df.show_days, showSeconds: df.show_seconds diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 3ddfa0f4dd..68628276a8 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -833,11 +833,11 @@ Object.assign(frappe.utils, { days: Math.floor(secs / (3600 * 24)), hours: Math.floor(secs % (3600 * 24) / 3600), minutes: Math.floor(secs % 3600 / 60), - seconds : Math.floor(secs % 60) + seconds: Math.floor(secs % 60) }; if (!duration_options.showDays) { - total_duration.hours = Math.floor(secs / 3600) - total_duration.days = 0 + total_duration.hours = Math.floor(secs / 3600); + total_duration.days = 0; } return total_duration; }, diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index 33d8594535..2b03b93f56 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -169,7 +169,7 @@ /* duration control */ .duration-picker { - position: relative; + position: absolute; z-index: 999; border-radius: 4px; @@ -178,7 +178,6 @@ border: 1px solid @border-color; padding-top: 10px; padding-left: 5px; - position: absolute; &:after, &:before { From 8933e71d906c6e16daa002ab478d123afd53842f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 18:52:34 +0530 Subject: [PATCH 24/75] test(ui): duration control --- cypress/integration/control_duration.js | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 cypress/integration/control_duration.js diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js new file mode 100644 index 0000000000..20634acb80 --- /dev/null +++ b/cypress/integration/control_duration.js @@ -0,0 +1,45 @@ +context('Control Duration', () => { + before(() => { + cy.login(); + cy.visit('/desk#workspace/Website'); + }); + + function get_dialog_with_duration(show_days=1, show_seconds=1) { + return cy.dialog({ + title: 'Duration', + fields: [{ + 'fieldname': 'duration', + 'fieldtype': 'Duration', + 'show_seconds': show_days, + 'show_days': show_seconds + }] + }); + } + + it('should set duration', () => { + get_dialog_with_duration().as('dialog') + cy.get('.frappe-control[data-fieldname=duration] input') + .first() + .click(); + cy.get('.duration-input[data-duration=days]') + .type(45) + .blur(); + cy.get('.duration-input[data-duration=minutes]') + .type(30) + .blur(); + cy.get('.frappe-control[data-fieldname=duration] input').should('have.value', '45d 30m'); + cy.get('.frappe-control[data-fieldname=duration] input').first().blur(); + cy.get('.duration-picker').should('not.be.visible'); + cy.get('@dialog').then(dialog => { + let value = dialog.get_value('duration'); + expect(value).to.equal(3889800); + }); + }); + + it('should hide days or seconds according to duration options', () => { + get_dialog_with_duration(0, 0).as('dialog'); + cy.get('.frappe-control[data-fieldname=duration] input').first().click(); + cy.get('.duration-input[data-duration=days]').should('not.be.visible'); + cy.get('.duration-input[data-duration=seconds]').should('not.be.visible'); + }); +}); \ No newline at end of file From 5a47f2da7fa2417f2c1624c7b6933d6dedce31e2 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 19:17:51 +0530 Subject: [PATCH 25/75] fix: sider and translations issues --- cypress/integration/control_duration.js | 2 +- frappe/public/js/frappe/form/formatters.js | 7 ++----- frappe/public/js/frappe/model/meta.js | 3 +-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js index 20634acb80..8ff6b8668d 100644 --- a/cypress/integration/control_duration.js +++ b/cypress/integration/control_duration.js @@ -17,7 +17,7 @@ context('Control Duration', () => { } it('should set duration', () => { - get_dialog_with_duration().as('dialog') + get_dialog_with_duration().as('dialog'); cy.get('.frappe-control[data-fieldname=duration] input') .first() .click(); diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 23c0ad5d5d..37c4db7428 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -142,10 +142,7 @@ frappe.form.formatters = { }, DateRange: function(value) { if($.isArray(value)) { - return __("{0} to {1}", [ - frappe.datetime.str_to_user(value[0]), - frappe.datetime.str_to_user(value[1]) - ]); + return __("{0} to {1}", [frappe.datetime.str_to_user(value[0]), frappe.datetime.str_to_user(value[1])]); } else { return value || ""; } @@ -188,7 +185,7 @@ frappe.form.formatters = { return value || ""; }, - Duration: function(value, docfield, doc) { + Duration: function(value, docfield) { if (value) { let duration_options = frappe.meta.get_duration_options(docfield); value = frappe.utils.get_formatted_duration(value, duration_options); diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index c6b5ca0b4a..f71689e409 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -161,8 +161,7 @@ $.extend(frappe.meta, { if(!out) { // eslint-disable-next-line - console.log(__('Warning: Unable to find {0} in any table related to {1}', [ - key, __(doctype)])); + console.log(__('Warning: Unable to find {0} in any table related to {1}', [key, __(doctype)])); } } return out; From e1fd1c0144a471029eae4834577b6d93855fd085 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 14 May 2020 11:32:32 +0530 Subject: [PATCH 26/75] fix: test --- cypress/integration/control_duration.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js index 8ff6b8668d..195200e824 100644 --- a/cypress/integration/control_duration.js +++ b/cypress/integration/control_duration.js @@ -23,11 +23,11 @@ context('Control Duration', () => { .click(); cy.get('.duration-input[data-duration=days]') .type(45) - .blur(); + .blur({force: true}); cy.get('.duration-input[data-duration=minutes]') .type(30) - .blur(); - cy.get('.frappe-control[data-fieldname=duration] input').should('have.value', '45d 30m'); + .blur({force: true}); + cy.get('.frappe-control[data-fieldname=duration] input').first().should('have.value', '45d 30m'); cy.get('.frappe-control[data-fieldname=duration] input').first().blur(); cy.get('.duration-picker').should('not.be.visible'); cy.get('@dialog').then(dialog => { From cfd7d6e09ffea9dbcfbfb30668fa896e408a3f35 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 14 May 2020 12:21:21 +0530 Subject: [PATCH 27/75] fix: test --- cypress/integration/control_duration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js index 195200e824..f304abd3d9 100644 --- a/cypress/integration/control_duration.js +++ b/cypress/integration/control_duration.js @@ -22,7 +22,7 @@ context('Control Duration', () => { .first() .click(); cy.get('.duration-input[data-duration=days]') - .type(45) + .type(45, {force: true}) .blur({force: true}); cy.get('.duration-input[data-duration=minutes]') .type(30) From 080a8b9ce0b51ea5adbd33a9244b48c16b39b163 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 14 May 2020 23:48:17 +0530 Subject: [PATCH 28/75] fix: add duration field options in custom field and customize form field --- .../doctype/custom_field/custom_field.json | 22 ++++++++++++++++++- .../customize_form_field.json | 22 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 6534bfd90e..d220e448df 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -16,6 +16,8 @@ "column_break_6", "fieldtype", "precision", + "show_seconds", + "show_days", "options", "fetch_from", "fetch_if_empty", @@ -378,12 +380,30 @@ "fieldname": "in_preview", "fieldtype": "Check", "label": "In Preview" + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_seconds", + "fieldtype": "Check", + "label": "Show Seconds", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_days", + "fieldtype": "Check", + "label": "Show Days", + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-glass", "idx": 1, "links": [], - "modified": "2020-04-30 09:15:24.394782", + "modified": "2020-05-14 23:43:00.123572", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index d1510e0858..5876666485 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -11,6 +11,8 @@ "label", "fieldtype", "fieldname", + "show_seconds", + "show_days", "reqd", "unique", "in_list_view", @@ -388,12 +390,30 @@ "fieldname": "in_preview", "fieldtype": "Check", "label": "In Preview" + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_seconds", + "fieldtype": "Check", + "label": "Show Seconds", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_days", + "fieldtype": "Check", + "label": "Show Days", + "show_days": 1, + "show_seconds": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-30 09:15:51.094586", + "modified": "2020-05-14 23:45:46.810869", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", From 4a51476aa788bced599800c8bb19ba161c82345f Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 18 May 2020 14:16:25 +0530 Subject: [PATCH 29/75] fix: bypass check in tests --- frappe/core/doctype/communication/communication.py | 4 +++- frappe/core/doctype/communication/test_communication.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 295d2464c8..edf1639de5 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -259,7 +259,9 @@ 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"): + 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]) for contact_name in contacts: diff --git a/frappe/core/doctype/communication/test_communication.py b/frappe/core/doctype/communication/test_communication.py index fb859586bb..6df90baaae 100644 --- a/frappe/core/doctype/communication/test_communication.py +++ b/frappe/core/doctype/communication/test_communication.py @@ -202,6 +202,8 @@ class TestCommunication(unittest.TestCase): self.assertIn(("Note", note.name), doc_links) def create_email_account(): + frappe.delete_doc_if_exists("Email Account", "_Test Comm Account 1") + frappe.flags.mute_emails = False frappe.flags.sent_mail = None From 068d55c68278e5409ed5273f2bd85f93c7ec1d70 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 20 May 2020 10:14:48 +0530 Subject: [PATCH 30/75] refactor: change duration options to de-facto snake case --- frappe/public/js/frappe/form/controls/duration.js | 8 ++++---- frappe/public/js/frappe/model/meta.js | 4 ++-- frappe/public/js/frappe/ui/filters/filters.js | 4 ++-- frappe/public/js/frappe/utils/utils.js | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index d6d512da9c..2014dbdabd 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -13,10 +13,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ ` ); this.$wrapper.append(this.$picker); - this.build_numeric_input("days", !this.duration_options.showDays); + this.build_numeric_input("days", !this.duration_options.show_days); this.build_numeric_input("hours", false); this.build_numeric_input("minutes", false); - this.build_numeric_input("seconds", !this.duration_options.showSeconds); + this.build_numeric_input("seconds", !this.duration_options.show_seconds); this.set_duration_picker(); this.$picker.hide(); this.bind_events(); @@ -115,10 +115,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ minutes: parseInt(this.inputs.minutes.val()), hours: parseInt(this.inputs.hours.val()), }; - if (this.duration_options.showDays) { + if (this.duration_options.show_days) { total_duration.days = parseInt(this.inputs.days.val()); } - if (this.duration_options.showSeconds) { + if (this.duration_options.show_seconds) { total_duration.seconds = parseInt(this.inputs.seconds.val()); } diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index f71689e409..5e3d709b4b 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -269,8 +269,8 @@ $.extend(frappe.meta, { get_duration_options: function(df) { let duration_options = { - showDays: df.show_days, - showSeconds: df.show_seconds + show_days: df.show_days, + show_seconds: df.show_seconds }; return duration_options; } diff --git a/frappe/public/js/frappe/ui/filters/filters.js b/frappe/public/js/frappe/ui/filters/filters.js index 9a394eade3..f8f0535b83 100644 --- a/frappe/public/js/frappe/ui/filters/filters.js +++ b/frappe/public/js/frappe/ui/filters/filters.js @@ -202,8 +202,8 @@ frappe.ui.FilterList = Class.extend({ value = {0:"No", 1:"Yes"}[cint(value)]; } else if (field.df.original_type === "Duration") { let duration_options = { - showDays: field.df.show_days, - showSeconds: field.df.show_seconds + show_days: field.df.show_days, + show_seconds: field.df.show_seconds }; value = frappe.utils.get_formatted_duration(value, duration_options); } diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 3cbbecea83..6b0ac3d437 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -836,7 +836,7 @@ Object.assign(frappe.utils, { minutes: Math.floor(secs % 3600 / 60), seconds: Math.floor(secs % 60) }; - if (!duration_options.showDays) { + if (!duration_options.show_days) { total_duration.hours = Math.floor(secs / 3600); total_duration.days = 0; } From a8262a06500ba8b97bd920b805f46262c720d021 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 20 May 2020 14:49:20 +0530 Subject: [PATCH 31/75] fix: Commit auto-updated files --- frappe/public/css/email.css | 47 ------------------- .../website_theme/standard/standard.json | 5 +- 2 files changed, 2 insertions(+), 50 deletions(-) diff --git a/frappe/public/css/email.css b/frappe/public/css/email.css index 92ac433fd2..40c6149927 100644 --- a/frappe/public/css/email.css +++ b/frappe/public/css/email.css @@ -1,82 +1,64 @@ /* csslint ignore:start */ - /* palette colors*/ - body { line-height: 1.5; color: #36414c; } - p { margin: 1em 0 !important; } - hr { border-top: 1px solid #d1d8dd; } - .body-table { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; } - .body-table td { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; } - .email-header, .email-body, .email-footer { width: 100% !important; min-width: 100% !important; } - .email-body { font-size: 14px; } - .email-footer { border-top: 1px solid #d1d8dd; font-size: 12px; } - .email-header { border: 1px solid #d1d8dd; border-radius: 4px 4px 0 0; } - .email-header .brand-image { width: 24px; height: 24px; display: block; } - .email-header-title { font-weight: bold; } - .body-table.has-header .email-body { border: 1px solid #d1d8dd; border-radius: 0 0 4px 4px; border-top: none; } - .body-table.has-header .email-footer { border-top: none; } - .email-footer-container { margin-top: 30px; } - .email-footer-container > div:not(:last-child) { margin-bottom: 5px; } - .email-unsubscribe a { color: #8d99a6; text-decoration: underline; } - .btn { text-decoration: none; padding: 7px 10px; @@ -84,24 +66,20 @@ hr { border: 1px solid; border-radius: 3px; } - .btn.btn-default { color: #fff; background-color: #f0f4f7; border-color: transparent; } - .btn.btn-primary { color: #fff; background-color: #5e64ff; border-color: #444bff; } - .table { width: 100%; border-collapse: collapse; } - .table td, .table th { padding: 8px; @@ -110,68 +88,53 @@ hr { border-top: 1px solid #d1d8dd; text-align: left; } - .table th { font-weight: bold; } - .table > thead > tr > th { vertical-align: middle; border-bottom: 2px solid #d1d8dd; } - .table > thead:first-child > tr:first-child > th { border-top: none; } - .table.table-bordered { border: 1px solid #d1d8dd; } - .table.table-bordered td, .table.table-bordered th { border: 1px solid #d1d8dd; } - .more-info { font-size: 80% !important; color: #8d99a6 !important; border-top: 1px solid #ebeff2; padding-top: 10px; } - .text-right { text-align: right !important; } - .text-center { text-align: center !important; } - .text-muted { color: #8d99a6 !important; } - .text-extra-muted { color: #d1d8dd !important; } - .text-regular { font-size: 14px; } - .text-medium { font-size: 12px; } - .text-small { font-size: 10px; } - .text-bold { font-weight: bold; } - .indicator { width: 8px; height: 8px; @@ -180,43 +143,33 @@ hr { display: inline-block; margin-right: 5px; } - .indicator.indicator-blue { background-color: #5e64ff; } - .indicator.indicator-green { background-color: #98d85b; } - .indicator.indicator-orange { background-color: #ffa00a; } - .indicator.indicator-red { background-color: #ff5858; } - .indicator.indicator-yellow { background-color: #feef72; } - .screenshot { box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.1); border: 1px solid #d1d8dd; margin: 8px 0; max-width: 100%; } - .list-unstyled { list-style-type: none; padding: 0; } - /* auto email report */ - .report-title { margin-bottom: 20px; } - /* csslint ignore:end */ diff --git a/frappe/website/website_theme/standard/standard.json b/frappe/website/website_theme/standard/standard.json index 9365d5be27..62a5cc8d7b 100644 --- a/frappe/website/website_theme/standard/standard.json +++ b/frappe/website/website_theme/standard/standard.json @@ -4,13 +4,12 @@ "button_shadows": 0, "creation": "2015-02-19 13:37:33.925909", "custom": 0, - "custom_css": "", "custom_overrides": "", "docstatus": 0, "doctype": "Website Theme", "font_properties": "400,500,600,700,800", "idx": 26, - "modified": "2020-05-09 16:44:18.508471", + "modified": "2020-05-20 14:47:12.938879", "modified_by": "Administrator", "module": "Website", "name": "Standard", @@ -18,4 +17,4 @@ "theme": "Standard", "theme_scss": "$enable-shadows: false;\n$enable-gradients: false;\n$enable-rounded: true;\n\n// Bootstrap Variable Overrides\n\n\n@import \"frappe/public/scss/website\";\n\n\n\n// Custom Theme\n", "theme_url": "/assets/css/standard_style.css" -} +} \ No newline at end of file From fd9fd7c940067d095b258caa0c881120afd722c8 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 22 May 2020 12:12:14 +0530 Subject: [PATCH 32/75] fix: backwards compatible verbose flag --- frappe/utils/backups.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index 087dec3111..e4e9b65e49 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -12,7 +12,9 @@ import frappe from frappe import _, conf from frappe.utils import cstr, get_url, now_datetime -_verbose = False +# backup variable for backwards compatibility +verbose = False +_verbose = verbose class BackupGenerator: From 087673c543d94fa1edc9e7b7025d847491da7567 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 22 May 2020 15:01:24 +0530 Subject: [PATCH 33/75] fix: chart auto color --- frappe/public/js/frappe/widgets/chart_widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index e5378cf2ab..35e5fc223e 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -577,7 +577,7 @@ export default class ChartWidget extends Widget { colors.push(field.color); }); } else if (["Line", "Bar"].includes(this.chart_doc.type)) { - colors = [this.chart_doc.color || "light-blue"]; + colors = [this.chart_doc.color || []]; } else if (this.chart_doc.type == "Heatmap") { colors = []; } From 38706e9dbecceafeddf9d9109dd4b80e530c3a54 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 22 May 2020 16:52:58 +0530 Subject: [PATCH 34/75] feat: sidebar spacing --- frappe/public/less/desktop.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/less/desktop.less b/frappe/public/less/desktop.less index 0b17d75861..08dc68b9bf 100644 --- a/frappe/public/less/desktop.less +++ b/frappe/public/less/desktop.less @@ -4,7 +4,7 @@ margin-top: 20px; .desk-sidebar { - width: 24rem; + width: 20rem; display: block; position: fixed; z-index: 1; @@ -69,7 +69,7 @@ } .desk-body { - padding-left: calc(24rem + 15px); + padding-left: 20rem; display: flex; flex-direction: column; height: 100%; From db0ad1912de2c908758f2f7200d952c3347435c2 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 22 May 2020 17:37:33 +0530 Subject: [PATCH 35/75] fix: call onload if defined in report script --- frappe/desk/doctype/dashboard_chart/dashboard_chart.js | 2 ++ frappe/public/js/frappe/widgets/chart_widget.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index e2be095fce..9053a9ab7f 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -353,6 +353,8 @@ frappe.ui.form.on('Dashboard Chart', { dialog.show(); //Set query report object so that it can be used while fetching filter values in the report frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list}); + frappe.query_reports[frm.doc.report_name].onload + && frappe.query_reports[frm.doc.report_name].onload(frappe.query_report); dialog.set_values(filters); }); }, diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index e5378cf2ab..f33b66638b 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -412,6 +412,8 @@ export default class ChartWidget extends Widget { dialog.show(); //Set query report object so that it can be used while fetching filter values in the report frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list}); + frappe.query_reports[this.chart_doc.report_name].onload + && frappe.query_reports[this.chart_doc.report_name].onload(frappe.query_report); dialog.set_values(this.filters); } From e18505feb5eb9b42dc62216852a0df3b941966d2 Mon Sep 17 00:00:00 2001 From: prssanna Date: Sat, 23 May 2020 14:26:07 +0530 Subject: [PATCH 36/75] fix: get_assignment fetches closed todo docs --- frappe/desk/form/load.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index f24f33df07..a705b2ce20 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -226,7 +226,7 @@ def get_assignments(dt, dn): filters={ 'reference_type': dt, 'reference_name': dn, - 'status': ('!=', 'Cancelled'), + 'status': ('not in', ['Closed', 'Cancelled']), }) return cl From 1a576df873d73f57d606842e022e5a204c4c88ed Mon Sep 17 00:00:00 2001 From: prssanna Date: Sat, 23 May 2020 14:30:05 +0530 Subject: [PATCH 37/75] fix: apply assignment rule on setting status as open --- frappe/core/doctype/communication/email.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 8793c60934..4498dc3b08 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -190,7 +190,7 @@ def update_parent_mins_to_first_response(doc): # if status has a "Replied" option, then update the status for received communication if ('Replied' in options) and doc.sent_or_received=="Received": - parent.db_set("status", "Open") + update_document_status_on_received_communication(parent) else: # update the modified date for document parent.update_modified() @@ -199,6 +199,11 @@ def update_parent_mins_to_first_response(doc): parent.run_method('notify_communication', doc) parent.notify_update() +def update_document_status_on_received_communication(doc): + from frappe.automation.doctype.assignment_rule.assignment_rule import apply + doc.db_set("status", "Open") + apply(doc) + def get_recipients_cc_and_bcc(doc, recipients, cc, bcc, fetched_from_email_account=False): doc.all_email_addresses = [] doc.sent_email_addresses = [] From 0d071217a7bc80e13febef83500762bb9b097337 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 23 May 2020 20:47:09 +0530 Subject: [PATCH 38/75] fix: edit duration from report view --- .../js/frappe/form/controls/duration.js | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 2014dbdabd..1041cb8c1a 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -17,7 +17,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ this.build_numeric_input("hours", false); this.build_numeric_input("minutes", false); this.build_numeric_input("seconds", !this.duration_options.show_seconds); - this.set_duration_picker(); + this.set_duration_picker_value(this.value); this.$picker.hide(); this.bind_events(); this.refresh(); @@ -53,8 +53,8 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ this.duration_options = frappe.meta.get_duration_options(this.df); }, - set_duration_picker() { - let total_duration = frappe.utils.seconds_to_duration(this.value, this.duration_options); + set_duration_picker_value: function(value) { + let total_duration = frappe.utils.seconds_to_duration(value, this.duration_options); if (this.$picker) { Object.keys(total_duration).forEach(duration => { @@ -75,12 +75,17 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ this.$picker.on("change", ".duration-input", () => { // duration changed in individual boxes clicked = false; - this.set_value(this.duration_to_seconds()); + let value = this.duration_to_seconds(); + this.set_value(value); this.set_focus(); }); this.$input.on("focus", () => { this.$picker.show(); + let is_picker_set = this.is_duration_picker_set(this.inputs); + if (!is_picker_set) { + this.set_duration_picker_value(this.value); + } }); this.$input.on("blur", () => { @@ -101,7 +106,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ refresh_input: function() { this._super(); this.set_duration_options(); - this.set_duration_picker(); + this.set_duration_picker_value(this.value); }, format_for_input: function(value) { @@ -136,5 +141,15 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ } } return value; + }, + + is_duration_picker_set(inputs) { + let is_set = false; + Object.values(inputs).forEach(duration => { + if (duration.prop("value") != 0) { + is_set = true; + } + }); + return is_set; } }); \ No newline at end of file From 1078b0e2da2108656a05231d94cf4b022b1d629c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 23 May 2020 21:48:47 +0530 Subject: [PATCH 39/75] fix(print preview): add formatter for duration fieldtype --- frappe/public/js/frappe/utils/utils.js | 1 + frappe/utils/data.py | 28 ++++++++++++++++++++++++++ frappe/utils/formatters.py | 6 +++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 6b0ac3d437..81034cb243 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -828,6 +828,7 @@ Object.assign(frappe.utils, { } return duration; }, + seconds_to_duration(value, duration_options) { let secs = value; let total_duration = { diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 9796aa3c4a..250917cc27 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -319,6 +319,34 @@ def format_datetime(datetime_string, format_string=None): formatted_datetime = datetime.strftime('%Y-%m-%d %H:%M:%S') return formatted_datetime +def format_duration(seconds, show_days=True): + total_duration = { + 'days': math.floor(seconds / (3600 * 24)), + 'hours': math.floor(seconds % (3600 * 24) / 3600), + 'minutes': math.floor(seconds % 3600 / 60), + 'seconds': math.floor(seconds % 60) + } + + if not show_days: + total_duration['hours'] = math.floor(seconds / 3600) + total_duration['days'] = 0 + + duration = '' + if total_duration: + if total_duration.get('days'): + duration += str(total_duration.get('days')) + 'd' + if total_duration.get('hours'): + duration += ' ' if len(duration) else '' + duration += str(total_duration.get('hours')) + 'h' + if total_duration.get('minutes'): + duration += ' ' if len(duration) else '' + duration += str(total_duration.get('minutes')) + 'm' + if total_duration.get('seconds'): + duration += ' ' if len(duration) else '' + duration += str(total_duration.get('seconds')) + 's' + + return duration + def get_weekdays(): return ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] diff --git a/frappe/utils/formatters.py b/frappe/utils/formatters.py index 46e3694e6f..d7646eeb71 100644 --- a/frappe/utils/formatters.py +++ b/frappe/utils/formatters.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe import datetime -from frappe.utils import formatdate, fmt_money, flt, cstr, cint, format_datetime, format_time +from frappe.utils import formatdate, fmt_money, flt, cstr, cint, format_datetime, format_time, format_duration from frappe.model.meta import get_field_currency, get_field_precision import re from six import string_types @@ -90,4 +90,8 @@ def format_value(value, df=None, doc=None, currency=None, translated=False): values = [v.get(link_field.fieldname, 'asdf') for v in value] return ', '.join(values) + elif df.get("fieldtype") == "Duration": + show_days = df.show_days + return format_duration(value, show_days) + return value From d3a4a02259d58f547db6322d94bebf8dd865a7c6 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 23 May 2020 22:33:58 +0530 Subject: [PATCH 40/75] fix: move duration options function from meta to utils.js --- frappe/public/js/frappe/form/controls/duration.js | 2 +- frappe/public/js/frappe/form/formatters.js | 2 +- frappe/public/js/frappe/model/meta.js | 8 -------- frappe/public/js/frappe/utils/utils.js | 8 ++++++++ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 1041cb8c1a..17dfc34827 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -50,7 +50,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, set_duration_options() { - this.duration_options = frappe.meta.get_duration_options(this.df); + this.duration_options = frappe.utils.get_duration_options(this.df); }, set_duration_picker_value: function(value) { diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 37c4db7428..9f4a2a61d6 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -187,7 +187,7 @@ frappe.form.formatters = { }, Duration: function(value, docfield) { if (value) { - let duration_options = frappe.meta.get_duration_options(docfield); + let duration_options = frappe.utils.get_duration_options(docfield); value = frappe.utils.get_formatted_duration(value, duration_options); } diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index 5e3d709b4b..c2fd6b1ae6 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -265,13 +265,5 @@ $.extend(frappe.meta, { precision = cint(frappe.defaults.get_default("float_precision")) || 3; } return precision; - }, - - get_duration_options: function(df) { - let duration_options = { - show_days: df.show_days, - show_seconds: df.show_seconds - }; - return duration_options; } }); diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 81034cb243..b5ff436383 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -843,6 +843,14 @@ Object.assign(frappe.utils, { } return total_duration; }, + + get_duration_options: function(docfield) { + let duration_options = { + show_days: docfield.show_days, + show_seconds: docfield.show_seconds + }; + return duration_options; + } }); // Array de duplicate From d7800143970ca7a10429ba294db80f09d715c062 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 23 May 2020 23:35:52 +0530 Subject: [PATCH 41/75] fix: export duration to seconds function to utils.js --- .../js/frappe/form/controls/duration.js | 39 +++++++++---------- frappe/public/js/frappe/utils/utils.js | 17 ++++++++ 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 17dfc34827..58df8e15e6 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -75,7 +75,13 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ this.$picker.on("change", ".duration-input", () => { // duration changed in individual boxes clicked = false; - let value = this.duration_to_seconds(); + let duration = this.get_duration(); + let value = frappe.utils.duration_to_seconds( + duration.days, + duration.hours, + duration.minutes, + duration.seconds + ); this.set_value(value); this.set_focus(); }); @@ -113,34 +119,25 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ return frappe.utils.get_formatted_duration(value, this.duration_options); }, - duration_to_seconds() { - let value = 0; + get_duration() { + // returns an object of days, hours, minutes and seconds from the inputs array + let total_duration = { + minutes: 0, + hours: 0, + days: 0, + seconds: 0 + }; if (this.inputs) { - let total_duration = { - minutes: parseInt(this.inputs.minutes.val()), - hours: parseInt(this.inputs.hours.val()), - }; + total_duration.minutes = parseInt(this.inputs.minutes.val()); + total_duration.hours = parseInt(this.inputs.hours.val()); if (this.duration_options.show_days) { total_duration.days = parseInt(this.inputs.days.val()); } if (this.duration_options.show_seconds) { total_duration.seconds = parseInt(this.inputs.seconds.val()); } - - if (total_duration.days) { - value += total_duration.days * 24 * 60 * 60; - } - if (total_duration.hours) { - value += total_duration.hours * 60 * 60; - } - if (total_duration.minutes) { - value += total_duration.minutes * 60; - } - if (total_duration.seconds) { - value += total_duration.seconds; - } } - return value; + return total_duration; }, is_duration_picker_set(inputs) { diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index b5ff436383..f985103803 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -844,6 +844,23 @@ Object.assign(frappe.utils, { return total_duration; }, + duration_to_seconds(days=0, hours=0, minutes=0, seconds=0) { + let value = 0; + if (days) { + value += days * 24 * 60 * 60; + } + if (hours) { + value += hours * 60 * 60; + } + if (minutes) { + value += minutes * 60; + } + if (seconds) { + value += seconds; + } + return value; + }, + get_duration_options: function(docfield) { let duration_options = { show_days: docfield.show_days, From 1f44b33e0d8179abd4ca8bdbe0e681c075825166 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 25 May 2020 17:44:51 +0530 Subject: [PATCH 42/75] fix: empty list not updated --- frappe/public/js/frappe/list/list_sidebar_group_by.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/list/list_sidebar_group_by.js b/frappe/public/js/frappe/list/list_sidebar_group_by.js index 3d64c42f6a..d9324297a7 100644 --- a/frappe/public/js/frappe/list/list_sidebar_group_by.js +++ b/frappe/public/js/frappe/list/list_sidebar_group_by.js @@ -103,7 +103,11 @@ frappe.views.ListGroupBy = class ListGroupBy { this.render_dropdown_items(field_count_list, fieldtype, dropdown); frappe.utils.setup_search(dropdown, '.group-by-item', '.group-by-value', 'data-name'); } else { - dropdown.find('.group-by-loading').html(`${__("No filters found")}`); + dropdown.html( + `
+ ${__("No filters found")} +
` + ); } }); }); From bfc16481ff17cf58a9d12d3f42621609fdad776f Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Mon, 25 May 2020 21:24:03 +0530 Subject: [PATCH 43/75] fix(timeline): Escape timeline content properly (#10472) --- .../public/js/frappe/form/footer/timeline.js | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/frappe/public/js/frappe/form/footer/timeline.js b/frappe/public/js/frappe/form/footer/timeline.js index beec168dfd..bb44408c2a 100644 --- a/frappe/public/js/frappe/form/footer/timeline.js +++ b/frappe/public/js/frappe/form/footer/timeline.js @@ -589,7 +589,6 @@ frappe.ui.form.Timeline = class Timeline { out.push(me.get_version_comment(version, message)); } } else { - p = p.map(frappe.utils.escape_html); const df = frappe.meta.get_docfield(me.frm.doctype, p[0], me.frm.docname); if (df && !df.hidden) { const field_display_status = frappe.perm.get_field_display_status(df, null, @@ -597,8 +596,8 @@ frappe.ui.form.Timeline = class Timeline { if (field_display_status === 'Read' || field_display_status === 'Write') { parts.push(__('{0} from {1} to {2}', [ __(df.label), - (frappe.ellipsis(frappe.utils.html2text(p[1]), 40) || '""').bold(), - (frappe.ellipsis(frappe.utils.html2text(p[2]), 40) || '""').bold() + me.format_content_for_timeline(p[1]), + me.format_content_for_timeline(p[2]) ])); } } @@ -608,9 +607,9 @@ frappe.ui.form.Timeline = class Timeline { if (parts.length) { let message; if (updater_reference_link) { - message = __("changed value of {0} {1}", [parts.join(', ').bold(), updater_reference_link]); + message = __("changed value of {0} {1}", [parts.join(', '), updater_reference_link]); } else { - message = __("changed value of {0}", [parts.join(', ').bold()]); + message = __("changed value of {0}", [parts.join(', ')]); } out.push(me.get_version_comment(version, message)); } @@ -618,23 +617,23 @@ frappe.ui.form.Timeline = class Timeline { // value changed in table field if (data.row_changed && data.row_changed.length) { - var parts = [], count = 0; + let parts = []; data.row_changed.every(function(row) { row[3].every(function(p) { var df = me.frm.fields_dict[row[0]] && frappe.meta.get_docfield(me.frm.fields_dict[row[0]].grid.doctype, p[0], me.frm.docname); - if(df && !df.hidden) { + if (df && !df.hidden) { var field_display_status = frappe.perm.get_field_display_status(df, null, me.frm.perm); - if(field_display_status === 'Read' || field_display_status === 'Write') { + if (field_display_status === 'Read' || field_display_status === 'Write') { parts.push(__('{0} from {1} to {2} in row #{3}', [ frappe.meta.get_label(me.frm.fields_dict[row[0]].grid.doctype, p[0]), - (frappe.ellipsis(p[1], 40) || '""').bold(), - (frappe.ellipsis(p[2], 40) || '""').bold(), + me.format_content_for_timeline(p[1]), + me.format_content_for_timeline(p[2]), row[1] ])); } @@ -657,20 +656,22 @@ frappe.ui.form.Timeline = class Timeline { // rows added / removed // __('added'), __('removed') # for translation, don't remove ['added', 'removed'].forEach(function(key) { - if(data[key] && data[key].length) { - parts = (data[key] || []).map(function(p) { + if (data[key] && data[key].length) { + let parts = (data[key] || []).map(function(p) { var df = frappe.meta.get_docfield(me.frm.doctype, p[0], me.frm.docname); - if(df && !df.hidden) { + if (df && !df.hidden) { var field_display_status = frappe.perm.get_field_display_status(df, null, me.frm.perm); - if(field_display_status === 'Read' || field_display_status === 'Write') { + if (field_display_status === 'Read' || field_display_status === 'Write') { return frappe.meta.get_label(me.frm.doctype, p[0]) } } }); - parts = parts.filter(function(p) { return p; }); - if(parts.length) { + parts = parts.filter(function(p) { + return p; + }); + if (parts.length) { out.push(me.get_version_comment(version, __("{0} rows for {1}", [__(key), parts.join(', ')]))); } @@ -717,6 +718,17 @@ frappe.ui.form.Timeline = class Timeline { } + format_content_for_timeline(content) { + // text to HTML + // limits content to 40 characters + // escapes HTML + // and makes it bold + content = frappe.utils.html2text(content); + content = frappe.ellipsis(content, 40) || '""'; + content = frappe.utils.escape_html(content); + return content.bold(); + } + delete_comment(name) { var me = this; From d07fe13890b1b031fe92401b575a9e1d90c89cdc Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 26 May 2020 00:37:11 +0530 Subject: [PATCH 44/75] fix: move code to communication.py and rename function --- .../doctype/communication/communication.py | 44 +++++++++++++++++-- frappe/core/doctype/communication/email.py | 43 +----------------- 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index abd24fb468..0f00df1a89 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -5,9 +5,9 @@ from __future__ import unicode_literals, absolute_import import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import validate_email_address, get_fullname, strip_html, cstr +from frappe.utils import validate_email_address, get_fullname, strip_html, cstr, time_diff_in_seconds from frappe.core.doctype.communication.email import (validate_email, - notify, _notify, update_parent_mins_to_first_response) + notify, _notify) from frappe.core.utils import get_parent_doc from frappe.utils.bot import BotReply from frappe.utils import parse_addr @@ -16,6 +16,7 @@ from email.utils import parseaddr from six.moves.urllib.parse import unquote from collections import Counter from frappe.contacts.doctype.contact.contact import get_contact_name +from frappe.automation.doctype.assignment_rule.assignment_rule import apply as apply_assignment_rule exclude_from_linked_with = True @@ -119,7 +120,7 @@ class Communication(Document): update_comment_in_doc(self) if self.comment_type != 'Updated': - update_parent_mins_to_first_response(self) + update_parent_document_on_communication(self) self.bot_reply() def on_trash(self): @@ -423,3 +424,40 @@ def get_email_without_link(email): email_host = email.split("@")[1] return "{0}@{1}".format(email_id, email_host) + +def update_parent_document_on_communication(doc): + """Update mins_to_first_communication of parent document based on who is replying.""" + + parent = get_parent_doc(doc) + if not parent: + return + + # update parent mins_to_first_communication only if we create the Email communication + # ignore in case of only Comment is added + if doc.communication_type == "Comment": + return + + status_field = parent.meta.get_field("status") + if status_field: + options = (status_field.options or '').splitlines() + + # if status has a "Replied" option, then update the status for received communication + if ('Replied' in options) and doc.sent_or_received=="Received": + parent.db_set("status", "Open") + apply_assignment_rule(parent) + else: + # update the modified date for document + parent.update_modified() + + update_mins_to_first_communication(parent, doc) + parent.run_method('notify_communication', doc) + parent.notify_update() + +def update_mins_to_first_communication(parent, communication): + if parent.meta.has_field('mins_to_first_response') and not parent.get('mins_to_first_response'): + if frappe.db.get_all('User', filters={'email': communication.sender, + 'user_type': 'System User', 'enabled': 1}, limit=1): + first_responded_on = communication.creation + if parent.meta.has_field('first_responded_on') and communication.sent_or_received == "Sent": + parent.db_set('first_responded_on', first_responded_on) + parent.db_set('mins_to_first_response', round(time_diff_in_seconds(first_responded_on, parent.creation) / 60), 2) \ No newline at end of file diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index 4498dc3b08..daf64d4b8b 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -9,7 +9,7 @@ import json from email.utils import formataddr from frappe.core.utils import get_parent_doc from frappe.utils import (get_url, get_formatted_email, cint, - validate_email_address, split_emails, time_diff_in_seconds, parse_addr, get_datetime) + validate_email_address, split_emails, parse_addr, get_datetime) from frappe.email.email_body import get_message_id import frappe.email.smtp import time @@ -172,38 +172,6 @@ def _notify(doc, print_html=None, print_format=None, attachments=None, print_letterhead=frappe.flags.print_letterhead ) -def update_parent_mins_to_first_response(doc): - """Update mins_to_first_communication of parent document based on who is replying.""" - - parent = get_parent_doc(doc) - if not parent: - return - - # update parent mins_to_first_communication only if we create the Email communication - # ignore in case of only Comment is added - if doc.communication_type == "Comment": - return - - status_field = parent.meta.get_field("status") - if status_field: - options = (status_field.options or '').splitlines() - - # if status has a "Replied" option, then update the status for received communication - if ('Replied' in options) and doc.sent_or_received=="Received": - update_document_status_on_received_communication(parent) - else: - # update the modified date for document - parent.update_modified() - - update_mins_to_first_communication(parent, doc) - parent.run_method('notify_communication', doc) - parent.notify_update() - -def update_document_status_on_received_communication(doc): - from frappe.automation.doctype.assignment_rule.assignment_rule import apply - doc.db_set("status", "Open") - apply(doc) - def get_recipients_cc_and_bcc(doc, recipients, cc, bcc, fetched_from_email_account=False): doc.all_email_addresses = [] doc.sent_email_addresses = [] @@ -504,15 +472,6 @@ def sendmail(communication_name, print_html=None, print_format=None, attachments traceback = frappe.log_error("frappe.core.doctype.communication.email.sendmail") raise -def update_mins_to_first_communication(parent, communication): - if parent.meta.has_field('mins_to_first_response') and not parent.get('mins_to_first_response'): - if frappe.db.get_all('User', filters={'email': communication.sender, - 'user_type': 'System User', 'enabled': 1}, limit=1): - first_responded_on = communication.creation - if parent.meta.has_field('first_responded_on') and communication.sent_or_received == "Sent": - parent.db_set('first_responded_on', first_responded_on) - parent.db_set('mins_to_first_response', round(time_diff_in_seconds(first_responded_on, parent.creation) / 60), 2) - @frappe.whitelist(allow_guest=True) def mark_email_as_seen(name=None): try: From cc6663dac454420fccb926a118a592299296f061 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 26 May 2020 00:37:46 +0530 Subject: [PATCH 45/75] fix: get only cancelled todo docs --- frappe/desk/form/load.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index a705b2ce20..f24f33df07 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -226,7 +226,7 @@ def get_assignments(dt, dn): filters={ 'reference_type': dt, 'reference_name': dn, - 'status': ('not in', ['Closed', 'Cancelled']), + 'status': ('!=', 'Cancelled'), }) return cl From b6a2df8b03b1ed5429213a8995ea110aa2986b57 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Tue, 26 May 2020 10:03:13 +0530 Subject: [PATCH 46/75] style: Fix cider - Also, remove redundant code --- frappe/core/doctype/communication/communication.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 0f00df1a89..d8f3bf9e77 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -2,19 +2,19 @@ # MIT License. See license.txt from __future__ import unicode_literals, absolute_import +from collections import Counter import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import validate_email_address, get_fullname, strip_html, cstr, time_diff_in_seconds -from frappe.core.doctype.communication.email import (validate_email, - notify, _notify) +from frappe.utils import validate_email_address, strip_html, cstr, time_diff_in_seconds +from frappe.core.doctype.communication.email import validate_email, notify, _notify from frappe.core.utils import get_parent_doc from frappe.utils.bot import BotReply from frappe.utils import parse_addr from frappe.core.doctype.comment.comment import update_comment_in_doc from email.utils import parseaddr from six.moves.urllib.parse import unquote -from collections import Counter +from frappe.utils.user import is_system_user from frappe.contacts.doctype.contact.contact import get_contact_name from frappe.automation.doctype.assignment_rule.assignment_rule import apply as apply_assignment_rule @@ -455,9 +455,8 @@ def update_parent_document_on_communication(doc): def update_mins_to_first_communication(parent, communication): if parent.meta.has_field('mins_to_first_response') and not parent.get('mins_to_first_response'): - if frappe.db.get_all('User', filters={'email': communication.sender, - 'user_type': 'System User', 'enabled': 1}, limit=1): + if is_system_user(communication.sender): first_responded_on = communication.creation if parent.meta.has_field('first_responded_on') and communication.sent_or_received == "Sent": parent.db_set('first_responded_on', first_responded_on) - parent.db_set('mins_to_first_response', round(time_diff_in_seconds(first_responded_on, parent.creation) / 60), 2) \ No newline at end of file + parent.db_set('mins_to_first_response', round(time_diff_in_seconds(first_responded_on, parent.creation) / 60), 2) From 8266a3aac307c6a34694f4541dec97413ccf334d Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 26 May 2020 11:20:25 +0530 Subject: [PATCH 47/75] fix: run cleanup only after new-site --- frappe/commands/site.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 82ed72dd5c..f0c4adb157 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -43,14 +43,16 @@ def new_site(site, mariadb_root_username=None, mariadb_root_password=None, admin _new_site(db_name, site, mariadb_root_username=mariadb_root_username, mariadb_root_password=mariadb_root_password, admin_password=admin_password, verbose=verbose, install_apps=install_app, source_sql=source_sql, force=force, - no_mariadb_socket=no_mariadb_socket, db_password=db_password, db_type=db_type, db_host=db_host, db_port=db_port) + no_mariadb_socket=no_mariadb_socket, db_password=db_password, db_type=db_type, db_host=db_host, + db_port=db_port, new_site=True) if len(frappe.utils.get_sites()) == 1: use(site) def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=None, admin_password=None, verbose=False, install_apps=None, source_sql=None, force=False, - no_mariadb_socket=False, reinstall=False, db_password=None, db_type=None, db_host=None, db_port=None): + no_mariadb_socket=False, reinstall=False, db_password=None, db_type=None, db_host=None, + db_port=None, new_site=False): """Install a new Frappe site""" if not force and os.path.exists(site): @@ -79,7 +81,10 @@ def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=N make_site_dirs() installing = touch_file(get_site_path('locks', 'installing.lock')) - atexit.register(_new_site_cleanup, site, mariadb_root_username, mariadb_root_password) + + if new_site: + # run cleanup only if new-site is called + atexit.register(_new_site_cleanup, site, mariadb_root_username, mariadb_root_password) install_db(root_login=mariadb_root_username, root_password=mariadb_root_password, db_name=db_name, admin_password=admin_password, verbose=verbose, source_sql=source_sql, force=force, reinstall=reinstall, @@ -97,7 +102,10 @@ def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=N print("*** Scheduler is", scheduler_status, "***") def _new_site_cleanup(site, mariadb_root_username, mariadb_root_password): - installing = get_site_path('locks', 'installing.lock') + try: + installing = get_site_path('locks', 'installing.lock') + except AttributeError: + installing = os.path.join(site, 'locks', 'installing.lock') if installing and os.path.exists(installing): if mariadb_root_password: From 84011d0bb7e5bca0f6bd100c8dd5ea61c8941b7a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 26 May 2020 12:28:44 +0530 Subject: [PATCH 48/75] feat: patch to delete onboarding slide doctype --- frappe/patches.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/patches.txt b/frappe/patches.txt index d164258c42..0a7d368ee2 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -283,3 +283,6 @@ frappe.patches.v13_0.remove_tailwind_from_page_builder frappe.patches.v13_0.rename_onboarding frappe.patches.v13_0.email_unsubscribe execute:frappe.delete_doc("Web Template", "Section with Left Image", force=1) +execute:frappe.delete_doc("DocType", "Onboarding Slide") +execute:frappe.delete_doc("DocType", "Onboarding Slide Field") +execute:frappe.delete_doc("DocType", "Onboarding Slide Help Link") \ No newline at end of file From 55bdc08ddb32cb6f044601ef3e2c37f5104988ec Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 26 May 2020 19:23:03 +0530 Subject: [PATCH 49/75] fix(translation): Do not share user translations --- frappe/translate.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frappe/translate.py b/frappe/translate.py index c3dbdfe7ea..91562382e6 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -126,15 +126,18 @@ def get_dict(fortype, name=None): # remove untranslated message_dict = {k:v for k, v in iteritems(message_dict) if k!=v} - if fortype=="boot": - message_dict.update(get_user_translations(frappe.local.lang)) - translation_assets[asset_key] = message_dict cache.hset("translation_assets", frappe.local.lang, translation_assets, shared=True) + if fortype=="boot": + message_dict.update(get_user_translations(frappe.local.lang)) + + return message_dict + return translation_assets[asset_key] + def get_dict_from_hooks(fortype, name): translated_dict = {} From dcf83b864550dd12b524995abacbc1456ac26e68 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 26 May 2020 23:03:59 +0530 Subject: [PATCH 50/75] fix: Update translation_map with user translation - Even while returning from cache --- frappe/translate.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/frappe/translate.py b/frappe/translate.py index 91562382e6..2fc2d3f328 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -119,23 +119,18 @@ def get_dict(fortype, name=None): messages += frappe.db.sql("select 'Role:', name from tabRole") messages += frappe.db.sql("select 'Module:', name from `tabModule Def`") - message_dict = make_dict_from_messages(messages) message_dict.update(get_dict_from_hooks(fortype, name)) - # remove untranslated message_dict = {k:v for k, v in iteritems(message_dict) if k!=v} - translation_assets[asset_key] = message_dict - cache.hset("translation_assets", frappe.local.lang, translation_assets, shared=True) - if fortype=="boot": - message_dict.update(get_user_translations(frappe.local.lang)) + translation_map = translation_assets[asset_key] + if fortype == "boot": + translation_map.update(get_user_translations(frappe.local.lang)) - return message_dict - - return translation_assets[asset_key] + return translation_map def get_dict_from_hooks(fortype, name): From bba7336b6ce55a23edca7fc9805100902500afd3 Mon Sep 17 00:00:00 2001 From: karthikeyan5 Date: Wed, 27 May 2020 08:39:50 +0530 Subject: [PATCH 51/75] fix(dropbox): dropbox timeout for large files --- .../integrations/doctype/dropbox_settings/dropbox_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py index 2a036f4838..4b595b1abf 100644 --- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py +++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py @@ -90,7 +90,7 @@ def backup_to_dropbox(upload_db_backup=True): dropbox_settings['access_token'] = access_token['oauth2_token'] set_dropbox_access_token(access_token['oauth2_token']) - dropbox_client = dropbox.Dropbox(dropbox_settings['access_token']) + dropbox_client = dropbox.Dropbox(dropbox_settings['access_token'], timeout=None) if upload_db_backup: if frappe.flags.create_new_backup: From 30a8472140c7b6e29dcb27314770825d94ed48ca Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 27 May 2020 12:03:47 +0530 Subject: [PATCH 52/75] feat: set scrollbar to thin --- frappe/public/less/frappe-datatable.less | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/less/frappe-datatable.less b/frappe/public/less/frappe-datatable.less index 39ed2eebf3..54eecf2b3d 100644 --- a/frappe/public/less/frappe-datatable.less +++ b/frappe/public/less/frappe-datatable.less @@ -36,6 +36,7 @@ .dt-scrollable { max-height: calc(100vh - 250px); min-height: 100px; + scrollbar-width: thin; } table td.dt-cell { From 6fd172c8f3a834df393c4c7c4d40fc09acb92b19 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Wed, 27 May 2020 13:11:05 +0200 Subject: [PATCH 53/75] fix: localize display of duration Use [contextual translations](https://frappe.io/docs/user/en/translations#5-adding-context-for-a-string), so that d, h, m, s make any sense to translators. --- frappe/public/js/frappe/utils/utils.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index f1677b5281..5d0a1403af 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -811,19 +811,19 @@ Object.assign(frappe.utils, { let total_duration = frappe.utils.seconds_to_duration(value, duration_options); if (total_duration.days) { - duration += total_duration.days + 'd'; + duration += total_duration.days + __('d', null, 'Field: Duration'); } if (total_duration.hours) { duration += (duration.length ? " " : ""); - duration += total_duration.hours + 'h'; + duration += total_duration.hours + __('h', null, 'Field: Duration'); } if (total_duration.minutes) { duration += (duration.length ? " " : ""); - duration += total_duration.minutes + 'm'; + duration += total_duration.minutes + __('m', null, 'Field: Duration'); } if (total_duration.seconds) { duration += (duration.length ? " " : ""); - duration += total_duration.seconds + 's'; + duration += total_duration.seconds + __('s', null, 'Field: Duration'); } } return duration; @@ -998,4 +998,4 @@ String.prototype.plural = function(revert) { } return this; -}; \ No newline at end of file +}; From 0396fd01dcad1c5bdf60977138b686648a41caf5 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Wed, 27 May 2020 13:14:45 +0200 Subject: [PATCH 54/75] fix: write out abbreviation --- frappe/public/js/frappe/utils/utils.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 5d0a1403af..24fa946fc4 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -811,19 +811,19 @@ Object.assign(frappe.utils, { let total_duration = frappe.utils.seconds_to_duration(value, duration_options); if (total_duration.days) { - duration += total_duration.days + __('d', null, 'Field: Duration'); + duration += total_duration.days + __('d', null, 'Days (Field: Duration)'); } if (total_duration.hours) { duration += (duration.length ? " " : ""); - duration += total_duration.hours + __('h', null, 'Field: Duration'); + duration += total_duration.hours + __('h', null, 'Hours (Field: Duration)'); } if (total_duration.minutes) { duration += (duration.length ? " " : ""); - duration += total_duration.minutes + __('m', null, 'Field: Duration'); + duration += total_duration.minutes + __('m', null, 'Minutes (Field: Duration)'); } if (total_duration.seconds) { duration += (duration.length ? " " : ""); - duration += total_duration.seconds + __('s', null, 'Field: Duration'); + duration += total_duration.seconds + __('s', null, 'Seconds (Field: Duration)'); } } return duration; From 702268767da3c3fb06bb1205e10150a6e98549b3 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 27 May 2020 17:06:56 +0530 Subject: [PATCH 55/75] fix: fetch date filters with default values set dynamically --- .../dashboard_chart/dashboard_chart.js | 23 +++++++++- .../public/js/frappe/widgets/chart_widget.js | 42 ++++++++++++++++--- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index 9053a9ab7f..8ce22d6d27 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -251,6 +251,7 @@ frappe.ui.form.on('Dashboard Chart', { render_filters_table: function(frm) { frm.set_df_property("filters_section", "hidden", 0); let is_document_type = frm.doc.chart_type!== 'Report' && frm.doc.chart_type!=='Custom'; + let is_dynamic_filter = f => ['Date', 'DateRange'].includes(f.fieldtype) && f.default; let wrapper = $(frm.get_field('filters_json').wrapper).empty(); let table = $(` @@ -268,6 +269,18 @@ frappe.ui.form.on('Dashboard Chart', { let filters = JSON.parse(frm.doc.filters_json || '[]'); var filters_set = false; + // Set dynamic filters for reports + if (frm.doc.chart_type == 'Report') { + let set_filters = false; + frm.chart_filters.forEach(f => { + if (is_dynamic_filter(f)) { + filters[f.fieldname] = f.default; + set_filters = true; + } + }); + set_filters && frm.set_value('filters_json', JSON.stringify(filters)); + } + let fields; if (is_document_type) { fields = [ @@ -291,7 +304,15 @@ frappe.ui.form.on('Dashboard Chart', { }); } } else if (frm.chart_filters.length) { - fields = frm.chart_filters.filter(f => f.fieldname); + fields = frm.chart_filters.filter(f => { + // Set dynamic filters as read only + if (is_dynamic_filter(f)) { + f.read_only = 1; + } + if (f.fieldname) { + return true; + } + }); fields.map( f => { if (filters[f.fieldname]) { let condition = '='; diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index d32ca6f4d8..77ea2847d7 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -97,7 +97,6 @@ export default class ChartWidget extends Widget { this.chart_settings = {}; } this.setup_container(); - this.prepare_chart_object(); if (!this.in_customize_mode) { this.action_area.empty(); this.prepare_chart_actions(); @@ -110,7 +109,10 @@ export default class ChartWidget extends Widget { this.render_time_series_filters(); } } - this.fetch_and_update_chart(); + frappe.run_serially([ + () => this.prepare_chart_object(), + () => this.fetch_and_update_chart(), + ]); }); } @@ -625,13 +627,41 @@ export default class ChartWidget extends Widget { } prepare_chart_object() { - let saved_filters = this.chart_settings.filters || null; - this.filters = - saved_filters || this.filters || JSON.parse(this.chart_doc.filters_json || "[]"); - if (this.chart_doc.type == 'Heatmap' && !this.chart_doc.heatmap_year) { this.chart_doc.heatmap_year = frappe.dashboard_utils.get_year(frappe.datetime.now_date()); } + + return this.set_chart_filters(); + } + + set_chart_filters() { + let user_saved_filters = this.chart_settings.filters || null; + let chart_saved_filters = JSON.parse(this.chart_doc.filters_json || "null"); + + if (this.chart_doc.chart_type == 'Report') { + return frappe.dashboard_utils + .get_filters_for_chart_type(this.chart_doc).then(filters => { + chart_saved_filters = this.update_default_date_filters(filters, chart_saved_filters); + this.filters = + user_saved_filters || this.filters || chart_saved_filters; + }); + } else { + this.filters = + user_saved_filters || this.filters || chart_saved_filters; + return Promise.resolve(); + } + } + + update_default_date_filters(report_filters, chart_filters) { + report_filters.map(f => { + if (['Date', 'DateRange'].includes(f.fieldtype) && f.default) { + if (chart_filters[f.fieldname]) { + chart_filters[f.fieldname] = f.default; + } + } + }); + + return chart_filters; } get_settings() { From 07df8b1083ccb58730a52e410d57f00ca9d7431b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 27 May 2020 21:13:01 +0530 Subject: [PATCH 56/75] fix: trigger after submit route hook after on_submit --- 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 bad7c877fc..aa0fc6e2ac 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -650,13 +650,14 @@ frappe.ui.form.Form = class FrappeForm { frappe.utils.play_sound("submit"); callback && callback(); me.script_manager.trigger("on_submit") - .then(() => resolve(me)); - if (frappe.route_hooks.after_submit) { - let route_callback = frappe.route_hooks.after_submit; - delete frappe.route_hooks.after_submit; - - route_callback(me); - } + .then(() => resolve(me)) + .then(() => { + if (frappe.route_hooks.after_submit) { + let route_callback = frappe.route_hooks.after_submit; + delete frappe.route_hooks.after_submit; + route_callback(me); + } + }); } }, btn, () => me.handle_save_fail(btn, on_error), resolve); }); From a4a49664a4bdf3694612b0007c2a192703c2fdd1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 28 May 2020 10:08:59 +0530 Subject: [PATCH 57/75] fix: driver find element by fieldname (bp #10494) (#10500) (cherry picked from commit e3e7d678447ac1c337b298376d557f09df859395) Co-authored-by: Shivam Mishra --- frappe/public/js/frappe/form/form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index aa0fc6e2ac..369e4a56d4 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1587,7 +1587,7 @@ frappe.ui.form.Form = class FrappeForm { let steps = frappe.tour[this.doctype].map(step => { let field = this.get_docfield(step.fieldname); return { - element: `.frappe-control[title='${step.fieldname}']`, + element: `.frappe-control[data-fieldname='${step.fieldname}']`, popover: { title: step.title || field.label, description: step.description From f8b3b818dfe401364749eb67c0b2a3972dca6b27 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 28 May 2020 12:36:34 +0530 Subject: [PATCH 58/75] fix: also set required filters --- frappe/public/js/frappe/widgets/chart_widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 77ea2847d7..8a0eca9eaf 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -655,7 +655,7 @@ export default class ChartWidget extends Widget { update_default_date_filters(report_filters, chart_filters) { report_filters.map(f => { if (['Date', 'DateRange'].includes(f.fieldtype) && f.default) { - if (chart_filters[f.fieldname]) { + if (f.reqd || chart_filters[f.fieldname]) { chart_filters[f.fieldname] = f.default; } } From 1515465df3ee0b90e40dedbf26da6473c5581930 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 28 May 2020 12:37:02 +0530 Subject: [PATCH 59/75] fix: don't show dynamic filters in dialog --- .../doctype/dashboard_chart/dashboard_chart.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index 8ce22d6d27..18dbb052d4 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -304,15 +304,8 @@ frappe.ui.form.on('Dashboard Chart', { }); } } else if (frm.chart_filters.length) { - fields = frm.chart_filters.filter(f => { - // Set dynamic filters as read only - if (is_dynamic_filter(f)) { - f.read_only = 1; - } - if (f.fieldname) { - return true; - } - }); + fields = frm.chart_filters.filter(f => f.fieldname); + fields.map( f => { if (filters[f.fieldname]) { let condition = '='; @@ -339,7 +332,7 @@ frappe.ui.form.on('Dashboard Chart', { let dialog = new frappe.ui.Dialog({ title: __('Set Filters'), - fields: fields, + fields: fields.filter(f => !is_dynamic_filter(f)), primary_action: function() { let values = this.get_values(); if (values) { @@ -376,6 +369,7 @@ frappe.ui.form.on('Dashboard Chart', { frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list}); frappe.query_reports[frm.doc.report_name].onload && frappe.query_reports[frm.doc.report_name].onload(frappe.query_report); + dialog.set_values(filters); }); }, From 6ff529d0b18b092b2000324fa7c14e63d166b0a6 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 28 May 2020 14:17:31 +0530 Subject: [PATCH 60/75] fix: don't include review type energy points in chart --- frappe/desk/page/user_profile/user_profile.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frappe/desk/page/user_profile/user_profile.js b/frappe/desk/page/user_profile/user_profile.js index c43ff27ba3..646c31f7a1 100644 --- a/frappe/desk/page/user_profile/user_profile.js +++ b/frappe/desk/page/user_profile/user_profile.js @@ -110,7 +110,11 @@ class UserProfile { render_line_chart() { - this.line_chart_filters = [['Energy Point Log', 'user', '=', this.user_id, false]]; + this.line_chart_filters = [ + ['Energy Point Log', 'user', '=', this.user_id, false], + ['Energy Point Log', 'type', '!=', 'Review', false] + ]; + this.line_chart_config = { timespan: 'Last Month', time_interval: 'Daily', @@ -186,7 +190,10 @@ class UserProfile { options: ['All', 'Auto', 'Criticism', 'Appreciation', 'Revert'], action: (selected_item) => { if (selected_item === 'All') { - if (this.line_chart_filters.length > 1) this.line_chart_filters.pop(); + this.line_chart_filters = [ + ['Energy Point Log', 'user', '=', this.user_id, false], + ['Energy Point Log', 'type', '!=', 'Review', false] + ]; } else { this.line_chart_filters[1] = ['Energy Point Log', 'type', '=', selected_item, false]; } From 073c5379d398b4cd36d3a71d20bf9e268813636e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 28 May 2020 09:44:39 +0000 Subject: [PATCH 61/75] refactor: handle exception in syncing dashboard (#10522) (cherry picked from commit e222961b3385cb3d44f84d38b2b325c75b869bcc) --- frappe/utils/dashboard.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/frappe/utils/dashboard.py b/frappe/utils/dashboard.py index f06f9272b8..c1884d62fb 100644 --- a/frappe/utils/dashboard.py +++ b/frappe/utils/dashboard.py @@ -89,10 +89,14 @@ def sync_dashboards(app=None): config = get_config(app_name, module_name) if config: frappe.flags.in_import = True - make_records(config.charts, "Dashboard Chart") - make_records(config.number_cards, "Number Card") - make_records(config.dashboards, "Dashboard") - frappe.flags.in_import = False + try: + make_records(config.charts, "Dashboard Chart") + make_records(config.number_cards, "Number Card") + make_records(config.dashboards, "Dashboard") + except Exception as e: + frappe.log_error(e, _("Dashboard Import Error")) + finally: + frappe.flags.in_import = False def make_records(config, doctype): if not config: From ed139a3b1d14f4b186e479f768853291656b1eff Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 28 May 2020 18:24:58 +0530 Subject: [PATCH 62/75] fix: check chart type before setting report object --- .../desk/doctype/dashboard_chart/dashboard_chart.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index 18dbb052d4..a10d3d96f2 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -365,10 +365,14 @@ frappe.ui.form.on('Dashboard Chart', { } dialog.show(); - //Set query report object so that it can be used while fetching filter values in the report - frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list}); - frappe.query_reports[frm.doc.report_name].onload - && frappe.query_reports[frm.doc.report_name].onload(frappe.query_report); + + if (frm.doc.chart_type == 'Report') { + //Set query report object so that it can be used while fetching filter values in the report + frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list}); + frappe.query_reports[frm.doc.report_name] + && frappe.query_reports[frm.doc.report_name].onload + && frappe.query_reports[frm.doc.report_name].onload(frappe.query_report); + } dialog.set_values(filters); }); From b6011e752b0c6fe49cde25bfa0c64f3e53989477 Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Fri, 29 May 2020 09:12:24 +0530 Subject: [PATCH 63/75] fix(Email): include Text in fieldtypes to sanitize (#10525) --- frappe/model/base_document.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index feeb96898a..106d21eb51 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -693,7 +693,7 @@ class BaseDocument(object): df = self.meta.get_field(fieldname) sanitized_value = value - if df and df.get("fieldtype") in ("Data", "Code", "Small Text") and df.get("options")=="Email": + if df and df.get("fieldtype") in ("Data", "Code", "Small Text", "Text") and df.get("options")=="Email": sanitized_value = sanitize_email(value) elif df and (df.get("ignore_xss_filter") From 7a6746bf32046cfa9bf11c89f51f50ea29084450 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 29 May 2020 11:42:34 +0530 Subject: [PATCH 64/75] fix(google-contacts): add additional params to prevent rate limiting (#10491) --- .../doctype/google_contacts/google_contacts.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.py b/frappe/integrations/doctype/google_contacts/google_contacts.py index 5874c79108..6455623281 100644 --- a/frappe/integrations/doctype/google_contacts/google_contacts.py +++ b/frappe/integrations/doctype/google_contacts/google_contacts.py @@ -147,11 +147,14 @@ def sync_contacts_from_google_contacts(g_contact): results = [] contacts_updated = 0 + sync_token = account.get_password(fieldname="next_sync_token", raise_exception=False) or None + contacts = frappe._dict() + while True: try: - sync_token = account.get_password(fieldname="next_sync_token", raise_exception=False) or None - contacts = google_contacts.people().connections().list(resourceName='people/me',syncToken=sync_token, - personFields="names,emailAddresses,organizations,phoneNumbers").execute() + contacts = google_contacts.people().connections().list(resourceName='people/me', pageToken=contacts.get("nextPageToken"), + syncToken=sync_token, pageSize=2000, requestSyncToken=True, personFields="names,emailAddresses,organizations,phoneNumbers").execute() + except HttpError as err: frappe.throw(_("Google Contacts - Could not sync contacts from Google Contacts {0}, error code {1}.").format(account.name, err.resp.status)) From 2e0c8ed0691b882ee20cd928e09870405fe382bf Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 12:56:07 +0530 Subject: [PATCH 65/75] fix: set route options before navigating --- frappe/public/js/frappe/widgets/shortcut_widget.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js index 572569839c..966528b391 100644 --- a/frappe/public/js/frappe/widgets/shortcut_widget.js +++ b/frappe/public/js/frappe/widgets/shortcut_widget.js @@ -31,7 +31,10 @@ export default class ShortcutWidget extends Widget { is_query_report: this.is_query_report, doctype: this.ref_doctype }); - + if (this.stats_filter) { + const get_filter = new Function(`return ${this.stats_filter}`); + frappe.route_options = get_filter(); + } frappe.set_route(route); }); } From b3e22411b93ac6e47bd853678b6592756702a1f8 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 13:01:01 +0530 Subject: [PATCH 66/75] fix: save filter JSON only if it is not empty --- frappe/public/js/frappe/widgets/widget_dialog.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js index 5c44533b37..d5cd6d9643 100644 --- a/frappe/public/js/frappe/widgets/widget_dialog.js +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -241,11 +241,14 @@ class ShortcutDialog extends WidgetDialog { if (this.dialog.get_value("type") == "DocType" && this.filter_group) { let filters = this.filter_group.get_filters(); - filters.forEach((arr) => { - stats_filter[arr[1]] = [arr[2], arr[3]]; - }); - data.stats_filter = JSON.stringify(stats_filter); + if (filters.length) { + filters.forEach((arr) => { + stats_filter[arr[1]] = [arr[2], arr[3]]; + }); + + data.stats_filter = JSON.stringify(stats_filter); + } } data.label = data.label From 447b3f65c840cee2468af57b02bae01572a46acf Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 13:05:51 +0530 Subject: [PATCH 67/75] feat: minor linting fixes --- frappe/public/js/frappe/widgets/shortcut_widget.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js index 966528b391..2c56450ab4 100644 --- a/frappe/public/js/frappe/widgets/shortcut_widget.js +++ b/frappe/public/js/frappe/widgets/shortcut_widget.js @@ -29,7 +29,7 @@ export default class ShortcutWidget extends Widget { name: this.link_to, type: this.type, is_query_report: this.is_query_report, - doctype: this.ref_doctype + doctype: this.ref_doctype, }); if (this.stats_filter) { const get_filter = new Function(`return ${this.stats_filter}`); @@ -85,4 +85,4 @@ export default class ShortcutWidget extends Widget { buttons.appendTo(this.action_area); } -} +} \ No newline at end of file From 5793be6565f1d192c55609809410ed7f1cbd09fa Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 07:41:26 +0000 Subject: [PATCH 68/75] chore: cleanup website desk pages (#10519) --- frappe/website/desk_page/website/website.json | 9 +++++++-- frappe/website/module_onboarding/website/website.json | 2 +- .../add_blog_category/add_blog_category.json | 6 ++++-- .../onboarding_step/create_blogger/create_blogger.json | 4 +++- .../enable_website_tracking/enable_website_tracking.json | 2 ++ .../introduction_to_website/introduction_to_website.json | 4 +++- .../onboarding_step/web_page_tour/web_page_tour.json | 6 ++++-- 7 files changed, 24 insertions(+), 9 deletions(-) diff --git a/frappe/website/desk_page/website/website.json b/frappe/website/desk_page/website/website.json index c42a17d404..9936f8d7e2 100644 --- a/frappe/website/desk_page/website/website.json +++ b/frappe/website/desk_page/website/website.json @@ -38,10 +38,11 @@ "docstatus": 0, "doctype": "Desk Page", "extends_another_page": 0, + "hide_custom": 0, "idx": 0, "is_standard": 1, "label": "Website", - "modified": "2020-05-05 18:17:13.232473", + "modified": "2020-05-28 13:53:10.736212", "modified_by": "Administrator", "module": "Website", "name": "Website", @@ -51,7 +52,7 @@ "pin_to_top": 0, "shortcuts": [ { - "color": "", + "color": "#cef6d1", "format": "{} Published", "label": "Blog Post", "link_to": "Blog Post", @@ -59,6 +60,7 @@ "type": "DocType" }, { + "color": "#cef6d1", "format": "{} Active", "label": "Blogger", "link_to": "Blogger", @@ -66,8 +68,11 @@ "type": "DocType" }, { + "color": "#cef6d1", + "format": "{} Published", "label": "Web Page", "link_to": "Web Page", + "stats_filter": "{ \"published\": 1 }", "type": "DocType" }, { diff --git a/frappe/website/module_onboarding/website/website.json b/frappe/website/module_onboarding/website/website.json index 606ef09811..c010d27eea 100644 --- a/frappe/website/module_onboarding/website/website.json +++ b/frappe/website/module_onboarding/website/website.json @@ -10,7 +10,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/website", "idx": 0, "is_complete": 0, - "modified": "2020-05-13 12:32:35.269025", + "modified": "2020-05-28 13:51:57.535269", "modified_by": "Administrator", "module": "Website", "name": "Website", diff --git a/frappe/website/onboarding_step/add_blog_category/add_blog_category.json b/frappe/website/onboarding_step/add_blog_category/add_blog_category.json index 5936e78c68..71924f8848 100644 --- a/frappe/website/onboarding_step/add_blog_category/add_blog_category.json +++ b/frappe/website/onboarding_step/add_blog_category/add_blog_category.json @@ -8,10 +8,12 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-04-30 19:06:10.750976", + "modified": "2020-05-28 13:51:53.157256", "modified_by": "Administrator", "name": "Add Blog Category", "owner": "Administrator", "reference_document": "Blog Category", - "title": "Add Blog Category" + "show_full_form": 0, + "title": "Add Blog Category", + "validate_action": 0 } \ No newline at end of file diff --git a/frappe/website/onboarding_step/create_blogger/create_blogger.json b/frappe/website/onboarding_step/create_blogger/create_blogger.json index 841325ca6a..48a0997025 100644 --- a/frappe/website/onboarding_step/create_blogger/create_blogger.json +++ b/frappe/website/onboarding_step/create_blogger/create_blogger.json @@ -13,5 +13,7 @@ "name": "Create Blogger", "owner": "Administrator", "reference_document": "Blogger", - "title": "Create Blogger" + "show_full_form": 0, + "title": "Create Blogger", + "validate_action": 0 } \ No newline at end of file diff --git a/frappe/website/onboarding_step/enable_website_tracking/enable_website_tracking.json b/frappe/website/onboarding_step/enable_website_tracking/enable_website_tracking.json index b37a704b1e..c445123243 100644 --- a/frappe/website/onboarding_step/enable_website_tracking/enable_website_tracking.json +++ b/frappe/website/onboarding_step/enable_website_tracking/enable_website_tracking.json @@ -14,6 +14,8 @@ "name": "Enable Website Tracking", "owner": "Administrator", "reference_document": "Website Settings", + "show_full_form": 0, "title": "Enable Website Tracking", + "validate_action": 0, "value_to_validate": "1" } \ No newline at end of file diff --git a/frappe/website/onboarding_step/introduction_to_website/introduction_to_website.json b/frappe/website/onboarding_step/introduction_to_website/introduction_to_website.json index f3c95657e8..29c1d8ff60 100644 --- a/frappe/website/onboarding_step/introduction_to_website/introduction_to_website.json +++ b/frappe/website/onboarding_step/introduction_to_website/introduction_to_website.json @@ -8,10 +8,12 @@ "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-04-30 19:06:10.578218", + "modified": "2020-05-28 13:51:51.485924", "modified_by": "Administrator", "name": "Introduction to Website", "owner": "Administrator", + "show_full_form": 0, "title": "Introduction to Website", + "validate_action": 0, "video_url": "https://www.youtube.com/watch?v=lyW6mfFBSNw" } \ No newline at end of file diff --git a/frappe/website/onboarding_step/web_page_tour/web_page_tour.json b/frappe/website/onboarding_step/web_page_tour/web_page_tour.json index 1ee98d69ee..e73610d847 100644 --- a/frappe/website/onboarding_step/web_page_tour/web_page_tour.json +++ b/frappe/website/onboarding_step/web_page_tour/web_page_tour.json @@ -8,10 +8,12 @@ "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-13 12:32:15.966570", + "modified": "2020-05-28 13:51:54.264456", "modified_by": "Administrator", "name": "Web Page Tour", "owner": "Administrator", "reference_document": "Web Page", - "title": "Learn about Web Pages" + "show_full_form": 0, + "title": "Learn about Web Pages", + "validate_action": 0 } \ No newline at end of file From a6bf1bd9791e3042a759d6bb4676b722b4be50d7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 13:11:06 +0530 Subject: [PATCH 69/75] fix: don't show count for empty filter --- .../js/frappe/widgets/shortcut_widget.js | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js index 2c56450ab4..1de7e8bf75 100644 --- a/frappe/public/js/frappe/widgets/shortcut_widget.js +++ b/frappe/public/js/frappe/widgets/shortcut_widget.js @@ -31,9 +31,10 @@ export default class ShortcutWidget extends Widget { is_query_report: this.is_query_report, doctype: this.ref_doctype, }); - if (this.stats_filter) { - const get_filter = new Function(`return ${this.stats_filter}`); - frappe.route_options = get_filter(); + + let filters = this.get_doctype_filter(); + if (this.type == "DocType" && filters) { + frappe.route_options = filters; } frappe.set_route(route); }); @@ -43,16 +44,26 @@ export default class ShortcutWidget extends Widget { if (this.in_customize_mode) return; this.widget.addClass("shortcut-widget-box"); - const get_filter = new Function(`return ${this.stats_filter}`); - if (this.type == "DocType" && this.stats_filter) { + + let filters = this.get_doctype_filter(); + if (this.type == "DocType" && filters) { frappe.db .count(this.link_to, { - filters: get_filter(), + filters: filters, }) .then((count) => this.set_count(count)); } } + get_doctype_filter() { + let count_filter = new Function(`return ${this.stats_filter}`)(); + if (count_filter) { + return count_filter; + } + + return null; + } + set_title() { if (this.icon) { this.title_field[0].innerHTML = `
From a7f941bcd9f2c0e302a462512c57b0ea49d32c1e Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 29 May 2020 13:17:14 +0530 Subject: [PATCH 70/75] fix: Better logging if form script handler throws (#10536) --- frappe/public/js/frappe/form/script_manager.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/script_manager.js b/frappe/public/js/frappe/form/script_manager.js index fd5b8d3856..1238bf141c 100644 --- a/frappe/public/js/frappe/form/script_manager.js +++ b/frappe/public/js/frappe/form/script_manager.js @@ -16,12 +16,22 @@ frappe.ui.form.get_event_handler_list = function(doctype, fieldname) { frappe.ui.form.on = frappe.ui.form.on_change = function(doctype, fieldname, handler) { var add_handler = function(fieldname, handler) { var handler_list = frappe.ui.form.get_event_handler_list(doctype, fieldname); - handler_list.push(handler); + + let _handler = (...args) => { + try { + handler(...args); + } catch (error) { + console.error(handler); + throw error; + } + } + + handler_list.push(_handler); // add last handler to events so it can be called as // frm.events.handler(frm) if(cur_frm && cur_frm.doctype===doctype) { - cur_frm.events[fieldname] = handler; + cur_frm.events[fieldname] = _handler; } } From 71442033c4d8f7aac24361cff66a4fd8b074bf03 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 15:14:18 +0530 Subject: [PATCH 71/75] fix: breadcrumb routing bug for desk pages --- frappe/boot.py | 2 ++ frappe/desk/doctype/desk_page/desk_page.py | 11 +++++++++++ frappe/public/js/frappe/views/breadcrumbs.js | 13 +++++++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index e615cc49fa..695a4d754b 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -19,6 +19,7 @@ from frappe.email.inbox import get_email_accounts from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabled from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_points +from frappe.model.base_document import get_controller from frappe.social.doctype.post.post import frequently_visited_links def get_bootinfo(): @@ -106,6 +107,7 @@ def load_desktop_data(bootinfo): from frappe.desk.desktop import get_desk_sidebar_items bootinfo.allowed_modules = get_modules_from_all_apps_for_user() bootinfo.allowed_workspaces = get_desk_sidebar_items(True) + bootinfo.module_page_map = get_controller("Desk Page").get_module_page_map() bootinfo.dashboards = frappe.get_all("Dashboard") def get_allowed_pages(cache=False): diff --git a/frappe/desk/doctype/desk_page/desk_page.py b/frappe/desk/doctype/desk_page/desk_page.py index dd9cc0706a..f14535cb5f 100644 --- a/frappe/desk/doctype/desk_page/desk_page.py +++ b/frappe/desk/doctype/desk_page/desk_page.py @@ -20,6 +20,17 @@ class DeskPage(Document): if frappe.conf.developer_mode and self.is_standard: export_to_files(record_list=[['Desk Page', self.name]], record_module=self.module) + @staticmethod + def get_module_page_map(): + filters = { + 'extends_another_page': 0, + 'for_user': '', + } + + pages = frappe.get_all("Desk Page", fields=["name", "module"], filters=filters, as_list=1) + + return { page[1]: page[0] for page in pages } + def disable_saving_as_standard(): return frappe.flags.in_install or \ frappe.flags.in_patch or \ diff --git a/frappe/public/js/frappe/views/breadcrumbs.js b/frappe/public/js/frappe/views/breadcrumbs.js index 1c1049391f..0058310e3f 100644 --- a/frappe/public/js/frappe/views/breadcrumbs.js +++ b/frappe/public/js/frappe/views/breadcrumbs.js @@ -89,16 +89,21 @@ frappe.breadcrumbs = { breadcrumbs.module = frappe.breadcrumbs.module_map[breadcrumbs.module]; } - if(frappe.get_module(breadcrumbs.module)) { + let current_module = breadcrumbs.module + // Check if a desk page exists + if (frappe.boot.module_page_map[breadcrumbs.module]) { + breadcrumbs.module = frappe.boot.module_page_map[breadcrumbs.module]; + } + + if(frappe.get_module(current_module)) { // if module access exists - var module_info = frappe.get_module(breadcrumbs.module), + var module_info = frappe.get_module(current_module), icon = module_info && module_info.icon, label = module_info ? module_info.label : breadcrumbs.module; - if(module_info && !module_info.blocked && frappe.visible_modules.includes(module_info.module_name)) { $(repl('
  • %(label)s
  • ', - { module: breadcrumbs.module, label: __(label) })) + { module: breadcrumbs.module, label: __(breadcrumbs.module) })) .appendTo($breadcrumbs); } } From 000864b12c239c09ccf6599d62088232288e2213 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 29 May 2020 14:44:29 +0530 Subject: [PATCH 72/75] chore: travis builds as needed Co-authored-by Suraj Shetty --- .travis.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.travis.yml b/.travis.yml index 30eb882256..291447c4b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,7 @@ cache: # https://docs.cypress.io/guides/guides/continuous-integration.html#Caching - ~/.cache + matrix: include: - name: "Python 3.7 MariaDB" @@ -46,6 +47,25 @@ matrix: script: bench --site test_site run-ui-tests frappe --headless before_install: + # do we really want to run travis? + - | + ONLY_DOCS_CHANGES=$(git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '\.(md|png|jpg|jpeg)$|^.github|LICENSE' ; echo $?) + ONLY_JS_CHANGES=$(git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '\.js$' ; echo $?) + ONLY_PY_CHANGES=$(git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '\.py$' ; echo $?) + + if [ $ONLY_DOCS_CHANGES == "1" ]; then + echo "Only docs were updated, stopping build process." + exit + fi + if [ $ONLY_JS_CHANGES == "1" && $TYPE == "server" ]; then + echo "Only JavaScript code was updated; Stopping Python build process." + exit + fi + if [ $ONLY_PY_CHANGES == "1" && $TYPE == "ui" ]; then + echo "Only Python code was updated, stopping Cypress build process." + exit + fi + # install wkhtmltopdf - wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz - tar -xf /tmp/wkhtmltox.tar.xz -C /tmp From fe387a24d8e899335577c0895ef6127975400afb Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 29 May 2020 16:31:49 +0530 Subject: [PATCH 73/75] fix: synatx errors from manual bash script runs --- .travis.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 291447c4b4..9fab56188b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,20 +53,20 @@ before_install: ONLY_JS_CHANGES=$(git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '\.js$' ; echo $?) ONLY_PY_CHANGES=$(git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '\.py$' ; echo $?) - if [ $ONLY_DOCS_CHANGES == "1" ]; then - echo "Only docs were updated, stopping build process." - exit + if [[ $ONLY_DOCS_CHANGES == "1" ]]; then + echo "Only docs were updated, stopping build process."; + exit; fi - if [ $ONLY_JS_CHANGES == "1" && $TYPE == "server" ]; then - echo "Only JavaScript code was updated; Stopping Python build process." - exit + if [[ $ONLY_JS_CHANGES == "1" && $TYPE == "server" ]]; then + echo "Only JavaScript code was updated; Stopping Python build process."; + exit; fi - if [ $ONLY_PY_CHANGES == "1" && $TYPE == "ui" ]; then - echo "Only Python code was updated, stopping Cypress build process." - exit + if [[ $ONLY_PY_CHANGES == "1" && $TYPE == "ui" ]]; then + echo "Only Python code was updated, stopping Cypress build process."; + exit; fi - # install wkhtmltopdf + # install wkhtmltopdf - wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz - tar -xf /tmp/wkhtmltox.tar.xz -C /tmp - sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf From b1dd6f854512370bebe6c6b31432c54669a4aa16 Mon Sep 17 00:00:00 2001 From: theopen-institute Date: Sat, 30 May 2020 07:34:51 +0545 Subject: [PATCH 74/75] Added warning if field with same fieldname already exists in doctype when creating custom field --- frappe/custom/doctype/custom_field/custom_field.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 21679c5bc7..1466c3309d 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -46,6 +46,9 @@ class CustomField(Document): if not self.fieldname: frappe.throw(_("Fieldname not set for Custom Field")) + if self.fieldname in fieldnames: + frappe.throw(_("A field with the name '{}' already exists in doctype {}.".format(self.fieldname, self.dt))) + if self.get('translatable', 0) and not supports_translation(self.fieldtype): self.translatable = 0 From e8a0b5b873cc8c7d5c007d6d37ddb5afd91fd730 Mon Sep 17 00:00:00 2001 From: theopen-institute Date: Sat, 30 May 2020 14:34:27 +0545 Subject: [PATCH 75/75] Update frappe/custom/doctype/custom_field/custom_field.py Co-authored-by: Chinmay Pai --- frappe/custom/doctype/custom_field/custom_field.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 1466c3309d..a24777a80a 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -47,7 +47,7 @@ class CustomField(Document): frappe.throw(_("Fieldname not set for Custom Field")) if self.fieldname in fieldnames: - frappe.throw(_("A field with the name '{}' already exists in doctype {}.".format(self.fieldname, self.dt))) + frappe.throw(_("A field with the name '{}' already exists in doctype {}.").format(self.fieldname, self.dt)) if self.get('translatable', 0) and not supports_translation(self.fieldtype): self.translatable = 0