From 39af16863d12b61d39ac1eccdc4a89aa8e831b49 Mon Sep 17 00:00:00 2001 From: Pruthvi Patel Date: Wed, 26 Jan 2022 11:19:30 +0530 Subject: [PATCH 01/71] feat: custom columns for `MultiSelectDialog` --- .../js/frappe/form/multi_select_dialog.js | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index bc0286e62d..e35c1d3a7c 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -1,6 +1,6 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { constructor(opts) { - /* Options: doctype, target, setters, get_query, action, add_filters_group, data_fields, primary_action_label */ + /* Options: doctype, target, setters, get_query, action, add_filters_group, data_fields, primary_action_label, columns */ Object.assign(this, opts); this.for_select = this.doctype == "[Select]"; if (!this.for_select) { @@ -384,23 +384,22 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { return this.results.filter(res => checked_values.includes(res.name)); } + get_datatable_columns() { + if(this.get_query && this.get_query().query && this.columns) return this.columns; + + if(Array.isArray(this.setters)) + return ["name", ...this.setters.map(df => df.fieldname)] + + return ["name", ...Object.keys(this.setters)] + } + make_list_row(result = {}) { var me = this; // Make a head row by default (if result not passed) let head = Object.keys(result).length === 0; let contents = ``; - let columns = ["name"]; - - if ($.isArray(this.setters)) { - for (let df of this.setters) { - columns.push(df.fieldname); - } - } else { - columns = columns.concat(Object.keys(this.setters)); - } - - columns.forEach(function (column) { + this.get_datatable_columns().forEach(function (column) { contents += `
${ head ? `${__(frappe.model.unscrub(column))}` @@ -470,7 +469,7 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { get_filters_from_setters() { let me = this; - let filters = this.get_query ? this.get_query().filters : {} || {}; + let filters = (this.get_query ? this.get_query().filters : {}) || {}; let filter_fields = []; if ($.isArray(this.setters)) { From eba3c95f55b06deca4c460e097a6e05e568a83e7 Mon Sep 17 00:00:00 2001 From: Pruthvi Patel Date: Wed, 26 Jan 2022 16:14:18 +0530 Subject: [PATCH 02/71] fix: sider issues --- frappe/public/js/frappe/form/multi_select_dialog.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/form/multi_select_dialog.js b/frappe/public/js/frappe/form/multi_select_dialog.js index e35c1d3a7c..5a670cee20 100644 --- a/frappe/public/js/frappe/form/multi_select_dialog.js +++ b/frappe/public/js/frappe/form/multi_select_dialog.js @@ -385,12 +385,12 @@ frappe.ui.form.MultiSelectDialog = class MultiSelectDialog { } get_datatable_columns() { - if(this.get_query && this.get_query().query && this.columns) return this.columns; + if (this.get_query && this.get_query().query && this.columns) return this.columns; - if(Array.isArray(this.setters)) - return ["name", ...this.setters.map(df => df.fieldname)] + if (Array.isArray(this.setters)) + return ["name", ...this.setters.map(df => df.fieldname)]; - return ["name", ...Object.keys(this.setters)] + return ["name", ...Object.keys(this.setters)]; } make_list_row(result = {}) { From 98faba837fb252e81646afe7e0e496f7522e36cb Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 28 Jan 2022 20:30:52 +0530 Subject: [PATCH 03/71] fix: update child table row's linked field on creating new connection --- frappe/public/js/frappe/form/form.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 5c0b6b1399..982f9ea120 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1670,16 +1670,10 @@ frappe.ui.form.Form = class FrappeForm { this.custom_buttons[__(this.custom_make_buttons[doctype])].trigger('click'); } else { frappe.model.with_doctype(doctype, function() { - var new_doc = frappe.model.get_new_doc(doctype); + var new_doc = frappe.model.get_new_doc(doctype, null, null, true); // set link fields (if found) - frappe.get_meta(doctype).fields.forEach(function(df) { - if(df.fieldtype==='Link' && df.options===me.doctype) { - new_doc[df.fieldname] = me.doc.name; - } else if (['Link', 'Dynamic Link'].includes(df.fieldtype) && me.doc[df.fieldname]) { - new_doc[df.fieldname] = me.doc[df.fieldname]; - } - }); + me.set_link_field(doctype, new_doc, me); frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); // frappe.set_route('Form', doctype, new_doc.name); @@ -1687,6 +1681,19 @@ frappe.ui.form.Form = class FrappeForm { } } + set_link_field(doctype, new_doc, me) { + frappe.get_meta(doctype).fields.forEach(function(df) { + if(df.fieldtype==='Link' && df.options===me.doctype) { + new_doc[df.fieldname] = me.doc.name; + } else if (['Link', 'Dynamic Link'].includes(df.fieldtype) && me.doc[df.fieldname]) { + new_doc[df.fieldname] = me.doc[df.fieldname]; + } else if (df.fieldtype==='Table' && df.options) { + let row = new_doc[df.fieldname][0]; + me.set_link_field(df.options, row, me); + } + }); + } + update_in_all_rows(table_fieldname, fieldname, value) { // update the child value in all tables where it is missing if(!value) return; From 80f733d675fe71d192f8f260859977bd5de659ca Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 28 Jan 2022 21:21:27 +0530 Subject: [PATCH 04/71] chore: improved code --- frappe/public/js/frappe/form/form.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 982f9ea120..aef146a569 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1663,17 +1663,17 @@ frappe.ui.form.Form = class FrappeForm { // make new doctype from the current form // will handover to `make_methods` if defined // or will create and match link fields - var me = this; + let me = this; if(this.make_methods && this.make_methods[doctype]) { return this.make_methods[doctype](this); } else if(this.custom_make_buttons && this.custom_make_buttons[doctype]) { this.custom_buttons[__(this.custom_make_buttons[doctype])].trigger('click'); } else { frappe.model.with_doctype(doctype, function() { - var new_doc = frappe.model.get_new_doc(doctype, null, null, true); + let new_doc = frappe.model.get_new_doc(doctype, null, null, true); // set link fields (if found) - me.set_link_field(doctype, new_doc, me); + me.set_link_field(doctype, new_doc); frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); // frappe.set_route('Form', doctype, new_doc.name); @@ -1681,15 +1681,16 @@ frappe.ui.form.Form = class FrappeForm { } } - set_link_field(doctype, new_doc, me) { + set_link_field(doctype, new_doc) { + let me = this; frappe.get_meta(doctype).fields.forEach(function(df) { - if(df.fieldtype==='Link' && df.options===me.doctype) { + if (df.fieldtype==='Link' && df.options===me.doctype) { new_doc[df.fieldname] = me.doc.name; } else if (['Link', 'Dynamic Link'].includes(df.fieldtype) && me.doc[df.fieldname]) { new_doc[df.fieldname] = me.doc[df.fieldname]; } else if (df.fieldtype==='Table' && df.options) { let row = new_doc[df.fieldname][0]; - me.set_link_field(df.options, row, me); + me.set_link_field(df.options, row); } }); } From c64a42288ddef1ee727de5c8037d615525f6ccf5 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 2 Feb 2022 12:23:12 +0530 Subject: [PATCH 05/71] chore: keep spaces around operators --- frappe/public/js/frappe/form/form.js | 34 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index aef146a569..5a09e6eef7 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -248,7 +248,7 @@ frappe.ui.form.Form = class FrappeForm { // on main doc frappe.model.on(me.doctype, "*", function(fieldname, value, doc) { // set input - if(doc.name===me.docname) { + if(doc.name === me.docname) { me.dirty(); let field = me.fields_dict[fieldname]; @@ -271,7 +271,7 @@ frappe.ui.form.Form = class FrappeForm { // using $.each to preserve df via closure $.each(table_fields, function(i, df) { frappe.model.on(df.options, "*", function(fieldname, value, doc) { - if(doc.parent===me.docname && doc.parentfield===df.fieldname) { + if(doc.parent === me.docname && doc.parentfield === df.fieldname) { me.dirty(); me.fields_dict[df.fieldname].grid.set_value(fieldname, value, doc); return me.script_manager.trigger(fieldname, doc.doctype, doc.name); @@ -366,7 +366,7 @@ frappe.ui.form.Form = class FrappeForm { // } if(switched) { - if(this.show_print_first && this.doc.docstatus===1) { + if(this.show_print_first && this.doc.docstatus === 1) { // show print view this.print_doc(); } @@ -374,9 +374,9 @@ frappe.ui.form.Form = class FrappeForm { // set status classes this.$wrapper.removeClass('validated-form') - .toggleClass('editable-form', this.doc.docstatus===0) - .toggleClass('submitted-form', this.doc.docstatus===1) - .toggleClass('cancelled-form', this.doc.docstatus===2); + .toggleClass('editable-form', this.doc.docstatus === 0) + .toggleClass('submitted-form', this.doc.docstatus === 1) + .toggleClass('cancelled-form', this.doc.docstatus === 2); this.show_conflict_message(); } @@ -415,7 +415,7 @@ frappe.ui.form.Form = class FrappeForm { frappe.throw(`Action ${action} not found`); } } - if (action.action_type==='Server Action') { + if (action.action_type === 'Server Action') { return frappe.xcall(action.action, {'doc': this.doc}).then((doc) => { if (doc.doctype) { // document is returned by the method, @@ -430,7 +430,7 @@ frappe.ui.form.Form = class FrappeForm { alert: true }); }); - } else if (action.action_type==='Route') { + } else if (action.action_type === 'Route') { return frappe.set_route(action.action); } } @@ -647,9 +647,9 @@ frappe.ui.form.Form = class FrappeForm { save_or_update() { if(this.save_disabled) return; - if(this.doc.docstatus===0) { + if(this.doc.docstatus === 0) { this.save(); - } else if(this.doc.docstatus===1 && this.doc.__unsaved) { + } else if(this.doc.docstatus === 1 && this.doc.__unsaved) { this.save("Update"); } } @@ -1013,7 +1013,7 @@ frappe.ui.form.Form = class FrappeForm { && !this.is_dirty() && !this.is_new() && !frappe.model.has_workflow(this.doctype) // show only if no workflow - && this.doc.docstatus===0) { + && this.doc.docstatus === 0) { this.dashboard.add_comment(__('Submit this document to confirm'), 'blue', true); } } @@ -1337,7 +1337,7 @@ frappe.ui.form.Form = class FrappeForm { } field_map(fnames, fn) { - if(typeof fnames==='string') { + if(typeof fnames === 'string') { if(fnames == '*') { fnames = Object.keys(this.fields_dict); } else { @@ -1493,7 +1493,7 @@ frappe.ui.form.Form = class FrappeForm { call(opts, args, callback) { var me = this; - if(typeof opts==='string') { + if(typeof opts === 'string') { // called as frm.call('do_this', {with_arg: 'arg'}); opts = { method: opts, @@ -1503,7 +1503,7 @@ frappe.ui.form.Form = class FrappeForm { }; } if(!opts.doc) { - if(opts.method.indexOf(".")===-1) + if(opts.method.indexOf(".") === -1) opts.method = frappe.model.get_server_module_name(me.doctype) + "." + opts.method; opts.original_callback = opts.callback; opts.callback = function(r) { @@ -1514,7 +1514,7 @@ frappe.ui.form.Form = class FrappeForm { var std_field_list = ["doctype"].concat(frappe.model.std_fields_list); for (var key in r.message) { - if (std_field_list.indexOf(key)===-1) { + if (std_field_list.indexOf(key) === -1) { opts.child[key] = r.message[key]; } } @@ -1684,11 +1684,11 @@ frappe.ui.form.Form = class FrappeForm { set_link_field(doctype, new_doc) { let me = this; frappe.get_meta(doctype).fields.forEach(function(df) { - if (df.fieldtype==='Link' && df.options===me.doctype) { + if (df.fieldtype === 'Link' && df.options === me.doctype) { new_doc[df.fieldname] = me.doc.name; } else if (['Link', 'Dynamic Link'].includes(df.fieldtype) && me.doc[df.fieldname]) { new_doc[df.fieldname] = me.doc[df.fieldname]; - } else if (df.fieldtype==='Table' && df.options) { + } else if (df.fieldtype === 'Table' && df.options) { let row = new_doc[df.fieldname][0]; me.set_link_field(df.options, row); } From 434ab616dfcd99ea27906bccc132040cc432e355 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 2 Feb 2022 12:43:58 +0530 Subject: [PATCH 06/71] fix: sider fix --- frappe/public/js/frappe/form/form.js | 176 +++++++++++++-------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 5a09e6eef7..0f88983c7e 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -44,7 +44,7 @@ frappe.ui.form.Form = class FrappeForm { setup_meta() { this.meta = frappe.get_doc('DocType', this.doctype); - if(this.meta.istable) { + if (this.meta.istable) { this.meta.in_dialog = 1; } @@ -248,7 +248,7 @@ frappe.ui.form.Form = class FrappeForm { // on main doc frappe.model.on(me.doctype, "*", function(fieldname, value, doc) { // set input - if(doc.name === me.docname) { + if (doc.name === me.docname) { me.dirty(); let field = me.fields_dict[fieldname]; @@ -271,7 +271,7 @@ frappe.ui.form.Form = class FrappeForm { // using $.each to preserve df via closure $.each(table_fields, function(i, df) { frappe.model.on(df.options, "*", function(fieldname, value, doc) { - if(doc.parent === me.docname && doc.parentfield === df.fieldname) { + if (doc.parent === me.docname && doc.parentfield === df.fieldname) { me.dirty(); me.fields_dict[df.fieldname].grid.set_value(fieldname, value, doc); return me.script_manager.trigger(fieldname, doc.doctype, doc.name); @@ -299,7 +299,7 @@ frappe.ui.form.Form = class FrappeForm { e.stopPropagation(); e.preventDefault(); - if(me.doc.__islocal) { + if (me.doc.__islocal) { frappe.msgprint(__("Please save before attaching.")); throw "attach error"; } @@ -322,19 +322,19 @@ frappe.ui.form.Form = class FrappeForm { refresh(docname) { var switched = docname ? true : false; - if(docname) { + if (docname) { this.switch_doc(docname); } cur_frm = this; - if(this.docname) { // document to show + if (this.docname) { // document to show this.save_disabled = false; // set the doc this.doc = frappe.get_doc(this.doctype, this.docname); // check permissions - if(!this.has_read_permission()) { + if (!this.has_read_permission()) { frappe.show_not_permitted(__(this.doctype) + " " + __(this.docname)); return; } @@ -353,7 +353,7 @@ frappe.ui.form.Form = class FrappeForm { } // do setup - if(!this.setup_done) { + if (!this.setup_done) { this.setup(); } @@ -361,12 +361,12 @@ frappe.ui.form.Form = class FrappeForm { this.trigger_onload(switched); // if print format is shown, refresh the format - // if(this.print_preview.wrapper.is(":visible")) { + // if (this.print_preview.wrapper.is(":visible")) { // this.print_preview.preview(); // } - if(switched) { - if(this.show_print_first && this.doc.docstatus === 1) { + if (switched) { + if (this.show_print_first && this.doc.docstatus === 1) { // show print view this.print_doc(); } @@ -449,7 +449,7 @@ frappe.ui.form.Form = class FrappeForm { } check_reload() { - if(this.doc && (!this.doc.__unsaved) && this.doc.__last_sync_on && + if (this.doc && (!this.doc.__unsaved) && this.doc.__last_sync_on && (new Date() - this.doc.__last_sync_on) > (this.refresh_if_stale_for * 1000)) { this.reload_doc(); return true; @@ -458,7 +458,7 @@ frappe.ui.form.Form = class FrappeForm { trigger_onload(switched) { this.cscript.is_onload = false; - if(!this.opendocs[this.docname]) { + if (!this.opendocs[this.docname]) { var me = this; this.cscript.is_onload = true; this.initialize_new_doc(); @@ -496,13 +496,13 @@ frappe.ui.form.Form = class FrappeForm { }); // update seen - if(this.meta.track_seen) { + if (this.meta.track_seen) { $('.list-id[data-name="'+ me.docname +'"]').addClass('seen'); } } render_form(switched) { - if(!this.meta.istable) { + if (!this.meta.istable) { this.layout.doc = this.doc; this.layout.attach_doc_and_docfields(); @@ -530,7 +530,7 @@ frappe.ui.form.Form = class FrappeForm { () => this.script_manager.trigger("refresh"), // call onload post render for callbacks to be fired () => { - if(this.cscript.is_onload) { + if (this.cscript.is_onload) { return this.script_manager.trigger("onload_post_render"); } }, @@ -547,7 +547,7 @@ frappe.ui.form.Form = class FrappeForm { this.cscript.is_onload && this.set_first_tab_as_active(); - if(!this.hidden) { + if (!this.hidden) { this.layout.show_empty_form_message(); } @@ -588,7 +588,7 @@ frappe.ui.form.Form = class FrappeForm { } cleanup_refresh() { - if(this.fields_dict['amended_from']) { + if (this.fields_dict['amended_from']) { if (this.doc.amended_from) { unhide_field('amended_from'); if (this.fields_dict['amendment_date']) unhide_field('amendment_date'); @@ -598,15 +598,15 @@ frappe.ui.form.Form = class FrappeForm { } } - if(this.fields_dict['trash_reason']) { - if(this.doc.trash_reason && this.doc.docstatus == 2) { + if (this.fields_dict['trash_reason']) { + if (this.doc.trash_reason && this.doc.docstatus == 2) { unhide_field('trash_reason'); } else { hide_field('trash_reason'); } } - if(this.meta.autoname && this.meta.autoname.substr(0,6)=='field:' && !this.doc.__islocal) { + if (this.meta.autoname && this.meta.autoname.substr(0,6)=='field:' && !this.doc.__islocal) { var fn = this.meta.autoname.substr(6); if (this.doc[fn]) { @@ -614,7 +614,7 @@ frappe.ui.form.Form = class FrappeForm { } } - if(this.meta.autoname=="naming_series:" && !this.doc.__islocal) { + if (this.meta.autoname=="naming_series:" && !this.doc.__islocal) { this.toggle_display("naming_series", false); } } @@ -622,12 +622,12 @@ frappe.ui.form.Form = class FrappeForm { refresh_header(switched) { // set title // main title - if(!this.meta.in_dialog || this.in_form) { + if (!this.meta.in_dialog || this.in_form) { frappe.utils.set_title(this.meta.issingle ? this.doctype : this.docname); } // show / hide buttons - if(this.toolbar) { + if (this.toolbar) { if (switched) { this.toolbar.current_status = undefined; } @@ -645,11 +645,11 @@ frappe.ui.form.Form = class FrappeForm { // SAVE save_or_update() { - if(this.save_disabled) return; + if (this.save_disabled) return; - if(this.doc.docstatus === 0) { + if (this.doc.docstatus === 0) { this.save(); - } else if(this.doc.docstatus === 1 && this.doc.__unsaved) { + } else if (this.doc.docstatus === 1 && this.doc.__unsaved) { this.save("Update"); } } @@ -669,13 +669,13 @@ frappe.ui.form.Form = class FrappeForm { validate_and_save(save_action, callback, btn, on_error, resolve, reject) { var me = this; - if(!save_action) save_action = "Save"; + if (!save_action) save_action = "Save"; this.validate_form_action(save_action, resolve); var after_save = function(r) { // to remove hash from URL to avoid scroll after save history.replaceState(null, null, ' '); - if(!r.exc) { + if (!r.exc) { if (["Save", "Update", "Amend"].indexOf(save_action)!==-1) { frappe.utils.play_sound("click"); } @@ -694,7 +694,7 @@ frappe.ui.form.Form = class FrappeForm { } me.refresh(); } else { - if(on_error) { + if (on_error) { on_error(); reject(); } @@ -708,20 +708,20 @@ frappe.ui.form.Form = class FrappeForm { console.error(e) } btn && $(btn).prop("disabled", false); - if(on_error) { + if (on_error) { on_error(); reject(); } }; - if(save_action != "Update") { + if (save_action != "Update") { // validate frappe.validated = true; frappe.run_serially([ () => this.script_manager.trigger("validate"), () => this.script_manager.trigger("before_save"), () => { - if(!frappe.validated) { + if (!frappe.validated) { fail(); return; } @@ -741,12 +741,12 @@ frappe.ui.form.Form = class FrappeForm { frappe.confirm(__("Permanently Submit {0}?", [this.docname]), function() { frappe.validated = true; me.script_manager.trigger("before_submit").then(function() { - if(!frappe.validated) { + if (!frappe.validated) { return me.handle_save_fail(btn, on_error); } me.save('Submit', function(r) { - if(r.exc) { + if (r.exc) { me.handle_save_fail(btn, on_error); } else { frappe.utils.play_sound("submit"); @@ -939,7 +939,7 @@ frappe.ui.form.Form = class FrappeForm { } if (!this.perm[0][perm_to_check] && !allowed_for_workflow) { - if(resolve) { + if (resolve) { // re-enable buttons resolve(); } @@ -995,8 +995,8 @@ frappe.ui.form.Form = class FrappeForm { } show_conflict_message() { - if(this.doc.__needs_refresh) { - if(this.doc.__unsaved) { + if (this.doc.__needs_refresh) { + if (this.doc.__unsaved) { this.dashboard.clear_headline(); this.dashboard.set_headline_alert(__("This form has been modified after you have loaded it") + '' @@ -1008,7 +1008,7 @@ frappe.ui.form.Form = class FrappeForm { } show_submit_message() { - if(this.meta.is_submittable + if (this.meta.is_submittable && this.perm[0] && this.perm[0].submit && !this.is_dirty() && !this.is_new() @@ -1019,9 +1019,9 @@ frappe.ui.form.Form = class FrappeForm { } show_web_link() { - if(!this.doc.__islocal && this.doc.__onload && this.doc.__onload.is_website_generator) { + if (!this.doc.__islocal && this.doc.__onload && this.doc.__onload.is_website_generator) { this.web_link && this.web_link.remove(); - if(this.doc.__onload.published) { + if (this.doc.__onload.published) { this.add_web_link("/" + this.doc.route); } } @@ -1038,16 +1038,16 @@ frappe.ui.form.Form = class FrappeForm { var dt = this.parent_doctype ? this.parent_doctype : this.doctype; this.perm = frappe.perm.get_perm(dt, this.doc); - if(!this.perm[0].read) { + if (!this.perm[0].read) { return 0; } return 1; } check_doctype_conflict(docname) { - if(this.doctype=='DocType' && docname=='DocType') { + if (this.doctype=='DocType' && docname=='DocType') { frappe.msgprint(__('Allowing DocType, DocType. Be careful!')); - } else if(this.doctype=='DocType') { + } else if (this.doctype=='DocType') { if (frappe.views.formview[docname] || frappe.pages['List/'+docname]) { window.location.reload(); // frappe.msgprint(__("Cannot open {0} when its instance is open", ['DocType'])) @@ -1066,16 +1066,16 @@ frappe.ui.form.Form = class FrappeForm { // notify this form of renamed records rename_notify(dt, old, name) { // from form - if(this.meta.istable) + if (this.meta.istable) return; - if(this.docname == old) + if (this.docname == old) this.docname = name; else return; // cleanup - if(this && this.opendocs[old] && frappe.meta.docfield_copy[dt]) { + if (this && this.opendocs[old] && frappe.meta.docfield_copy[dt]) { // delete docfield copy frappe.meta.docfield_copy[dt][name] = frappe.meta.docfield_copy[dt][old]; delete frappe.meta.docfield_copy[dt][old]; @@ -1084,7 +1084,7 @@ frappe.ui.form.Form = class FrappeForm { delete this.opendocs[old]; this.opendocs[name] = true; - if(this.meta.in_dialog || !this.in_form) { + if (this.meta.in_dialog || !this.in_form) { return; } @@ -1159,7 +1159,7 @@ frappe.ui.form.Form = class FrappeForm { newdoc.idx = null; newdoc.__run_link_triggers = false; - if(onload) { + if (onload) { onload(newdoc); } frappe.set_route('Form', newdoc.doctype, newdoc.name); @@ -1168,7 +1168,7 @@ frappe.ui.form.Form = class FrappeForm { reload_doc() { this.check_doctype_conflict(this.docname); - if(!this.doc.__islocal) { + if (!this.doc.__islocal) { frappe.model.remove_from_locals(this.doctype, this.docname); return frappe.model.with_doc(this.doctype, this.docname, () => { this.refresh(); @@ -1315,9 +1315,9 @@ frappe.ui.form.Form = class FrappeForm { $.each(fields_list, function(i, fname) { var docfield = frappe.meta.docfield_map[doctype][fname]; - if(docfield) { + if (docfield) { var label = __(docfield.label || "").replace(/\([^\)]*\)/g, ""); // eslint-disable-line - if(parentfield) { + if (parentfield) { grid_field_label_map[doctype + "-" + fname] = label.trim() + " (" + __(currency) + ")"; } else { @@ -1337,8 +1337,8 @@ frappe.ui.form.Form = class FrappeForm { } field_map(fnames, fn) { - if(typeof fnames === 'string') { - if(fnames == '*') { + if (typeof fnames === 'string') { + if (fnames == '*') { fnames = Object.keys(this.fields_dict); } else { fnames = [fnames]; @@ -1347,7 +1347,7 @@ frappe.ui.form.Form = class FrappeForm { for (var i=0, l=fnames.length; i { - if(!unique_keys.includes(key)) { + if (!unique_keys.includes(key)) { d[key] = values[key]; } }); @@ -1452,9 +1452,9 @@ frappe.ui.form.Form = class FrappeForm { var me = this; var _set = function(f, v) { var fieldobj = me.fields_dict[f]; - if(fieldobj) { - if(!if_missing || !frappe.model.has_value(me.doctype, me.doc.name, f)) { - if(frappe.model.table_fields.includes(fieldobj.df.fieldtype) && $.isArray(v)) { + if (fieldobj) { + if (!if_missing || !frappe.model.has_value(me.doctype, me.doc.name, f)) { + if (frappe.model.table_fields.includes(fieldobj.df.fieldtype) && $.isArray(v)) { frappe.model.clear_table(me.doc, fieldobj.df.fieldname); @@ -1477,13 +1477,13 @@ frappe.ui.form.Form = class FrappeForm { } }; - if(typeof field=="string") { + if (typeof field=="string") { return _set(field, value); - } else if($.isPlainObject(field)) { + } else if ($.isPlainObject(field)) { let tasks = []; for (let f in field) { let v = field[f]; - if(me.get_field(f)) { + if (me.get_field(f)) { tasks.push(() => _set(f, v)); } } @@ -1493,7 +1493,7 @@ frappe.ui.form.Form = class FrappeForm { call(opts, args, callback) { var me = this; - if(typeof opts === 'string') { + if (typeof opts === 'string') { // called as frm.call('do_this', {with_arg: 'arg'}); opts = { method: opts, @@ -1502,13 +1502,13 @@ frappe.ui.form.Form = class FrappeForm { callback: callback }; } - if(!opts.doc) { - if(opts.method.indexOf(".") === -1) + if (!opts.doc) { + if (opts.method.indexOf(".") === -1) opts.method = frappe.model.get_server_module_name(me.doctype) + "." + opts.method; opts.original_callback = opts.callback; opts.callback = function(r) { - if($.isPlainObject(r.message)) { - if(opts.child) { + if ($.isPlainObject(r.message)) { + if (opts.child) { // update child doc opts.child = locals[opts.child.doctype][opts.child.name]; @@ -1530,7 +1530,7 @@ frappe.ui.form.Form = class FrappeForm { } else { opts.original_callback = opts.callback; opts.callback = function(r) { - if(!r.exc) me.refresh_fields(); + if (!r.exc) me.refresh_fields(); opts.original_callback && opts.original_callback(r); }; @@ -1571,7 +1571,7 @@ frappe.ui.form.Form = class FrappeForm { } get_title() { - if(this.meta.title_field) { + if (this.meta.title_field) { return this.doc[this.meta.title_field]; } else { return this.doc.name; @@ -1585,11 +1585,11 @@ frappe.ui.form.Form = class FrappeForm { // handle TableMultiselect child fields let _selected = []; - if(me.fields_dict[df.fieldname].grid) { + if (me.fields_dict[df.fieldname].grid) { _selected = me.fields_dict[df.fieldname].grid.get_selected(); } - if(_selected.length) { + if (_selected.length) { selected[df.fieldname] = _selected; } }); @@ -1599,11 +1599,11 @@ frappe.ui.form.Form = class FrappeForm { set_indicator_formatter(fieldname, get_color, get_text) { // get doctype from parent var doctype; - if(frappe.meta.docfield_map[this.doctype][fieldname]) { + if (frappe.meta.docfield_map[this.doctype][fieldname]) { doctype = this.doctype; } else { frappe.meta.get_table_fields(this.doctype).every(function(df) { - if(frappe.meta.docfield_map[df.options][fieldname]) { + if (frappe.meta.docfield_map[df.options][fieldname]) { doctype = df.options; return false; } else { @@ -1614,11 +1614,11 @@ frappe.ui.form.Form = class FrappeForm { frappe.meta.docfield_map[doctype][fieldname].formatter = function(value, df, options, doc) { - if(value) { + if (value) { var label; - if(get_text) { + if (get_text) { label = get_text(doc); - } else if(frappe.form.link_formatters[df.options]) { + } else if (frappe.form.link_formatters[df.options]) { label = frappe.form.link_formatters[df.options](value, doc); } else { label = value; @@ -1637,21 +1637,21 @@ frappe.ui.form.Form = class FrappeForm { // return true or false if the user can make a particlar doctype // will check permission, `can_make_methods` if exists, or will decided on // basis of whether the document is submittable - if(!frappe.model.can_create(doctype)) { + if (!frappe.model.can_create(doctype)) { return false; } - if(this.custom_make_buttons && this.custom_make_buttons[doctype]) { + if (this.custom_make_buttons && this.custom_make_buttons[doctype]) { // custom buttons are translated and so are the keys const key = __(this.custom_make_buttons[doctype]); // if the button is present, then show make return !!this.custom_buttons[key]; } - if(this.can_make_methods && this.can_make_methods[doctype]) { + if (this.can_make_methods && this.can_make_methods[doctype]) { return this.can_make_methods[doctype](this); } else { - if(this.meta.is_submittable && !this.doc.docstatus==1) { + if (this.meta.is_submittable && !this.doc.docstatus==1) { return false; } else { return true; @@ -1664,9 +1664,9 @@ frappe.ui.form.Form = class FrappeForm { // will handover to `make_methods` if defined // or will create and match link fields let me = this; - if(this.make_methods && this.make_methods[doctype]) { + if (this.make_methods && this.make_methods[doctype]) { return this.make_methods[doctype](this); - } else if(this.custom_make_buttons && this.custom_make_buttons[doctype]) { + } else if (this.custom_make_buttons && this.custom_make_buttons[doctype]) { this.custom_buttons[__(this.custom_make_buttons[doctype])].trigger('click'); } else { frappe.model.with_doctype(doctype, function() { @@ -1697,10 +1697,10 @@ frappe.ui.form.Form = class FrappeForm { update_in_all_rows(table_fieldname, fieldname, value) { // update the child value in all tables where it is missing - if(!value) return; + if (!value) return; var cl = this.doc[table_fieldname] || []; for(var i = 0; i < cl.length; i++){ - if(!cl[i][fieldname]) cl[i][fieldname] = value; + if (!cl[i][fieldname]) cl[i][fieldname] = value; } refresh_field("items"); } From ae91bfa49ed26b083fd181bcdd716819d1edc188 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 2 Feb 2022 13:04:24 +0530 Subject: [PATCH 07/71] fix: sider fix --- frappe/public/js/frappe/form/form.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 0f88983c7e..2fd93295ce 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -282,7 +282,7 @@ frappe.ui.form.Form = class FrappeForm { setup_notify_on_rename() { $(document).on('rename', (ev, dt, old_name, new_name) => { - if (dt==this.doctype) + if (dt == this.doctype) this.rename_notify(dt, old_name, new_name); }); } @@ -606,7 +606,7 @@ frappe.ui.form.Form = class FrappeForm { } } - if (this.meta.autoname && this.meta.autoname.substr(0,6)=='field:' && !this.doc.__islocal) { + if (this.meta.autoname && this.meta.autoname.substr(0, 6) == 'field:' && !this.doc.__islocal) { var fn = this.meta.autoname.substr(6); if (this.doc[fn]) { @@ -614,7 +614,7 @@ frappe.ui.form.Form = class FrappeForm { } } - if (this.meta.autoname=="naming_series:" && !this.doc.__islocal) { + if (this.meta.autoname == "naming_series:" && !this.doc.__islocal) { this.toggle_display("naming_series", false); } } @@ -676,7 +676,7 @@ frappe.ui.form.Form = class FrappeForm { // to remove hash from URL to avoid scroll after save history.replaceState(null, null, ' '); if (!r.exc) { - if (["Save", "Update", "Amend"].indexOf(save_action)!==-1) { + if (["Save", "Update", "Amend"].indexOf(save_action) !== -1) { frappe.utils.play_sound("click"); } @@ -984,7 +984,7 @@ frappe.ui.form.Form = class FrappeForm { // trigger link fields which have default values set if (this.is_new() && this.doc.__run_link_triggers) { $.each(this.fields_dict, function(fieldname, field) { - if (field.df.fieldtype=="Link" && this.doc[fieldname]) { + if (field.df.fieldtype == "Link" && this.doc[fieldname]) { // triggers add fetch, sets value in model and runs triggers field.set_value(this.doc[fieldname], true); } @@ -1045,9 +1045,9 @@ frappe.ui.form.Form = class FrappeForm { } check_doctype_conflict(docname) { - if (this.doctype=='DocType' && docname=='DocType') { + if (this.doctype == 'DocType' && docname == 'DocType') { frappe.msgprint(__('Allowing DocType, DocType. Be careful!')); - } else if (this.doctype=='DocType') { + } else if (this.doctype == 'DocType') { if (frappe.views.formview[docname] || frappe.pages['List/'+docname]) { window.location.reload(); // frappe.msgprint(__("Cannot open {0} when its instance is open", ['DocType'])) @@ -1477,7 +1477,7 @@ frappe.ui.form.Form = class FrappeForm { } }; - if (typeof field=="string") { + if (typeof field == "string") { return _set(field, value); } else if ($.isPlainObject(field)) { let tasks = []; @@ -1651,7 +1651,7 @@ frappe.ui.form.Form = class FrappeForm { if (this.can_make_methods && this.can_make_methods[doctype]) { return this.can_make_methods[doctype](this); } else { - if (this.meta.is_submittable && !this.doc.docstatus==1) { + if (this.meta.is_submittable && !this.doc.docstatus == 1) { return false; } else { return true; @@ -1699,7 +1699,7 @@ frappe.ui.form.Form = class FrappeForm { // update the child value in all tables where it is missing if (!value) return; var cl = this.doc[table_fieldname] || []; - for(var i = 0; i < cl.length; i++){ + for (var i = 0; i < cl.length; i++) { if (!cl[i][fieldname]) cl[i][fieldname] = value; } refresh_field("items"); From 891d1fbd862f91d543e8aa92e73bb4aea467a2d8 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 2 Feb 2022 16:25:10 +0530 Subject: [PATCH 08/71] refactor: Site Migration --- frappe/commands/site.py | 12 +-- frappe/migrate.py | 191 +++++++++++++++++++++++++++------------- 2 files changed, 135 insertions(+), 68 deletions(-) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 62488525b0..1684f26d49 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -447,21 +447,17 @@ def disable_user(context, email): @pass_context def migrate(context, skip_failing=False, skip_search_index=False): "Run patches, sync schema and rebuild files/translations" - from frappe.migrate import migrate + from frappe.migrate import SiteMigration for site in context.sites: click.secho(f"Migrating {site}", fg="green") - frappe.init(site=site) - frappe.connect() try: - migrate( - context.verbose, + SiteMigration( skip_failing=skip_failing, - skip_search_index=skip_search_index - ) + skip_search_index=skip_search_index, + ).run(site=site) finally: print() - frappe.destroy() if not context.sites: raise SiteNotSpecifiedError diff --git a/frappe/migrate.py b/frappe/migrate.py index d13fe858f7..eabd0ff3e0 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -1,30 +1,54 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE import json import os -import sys +from textwrap import dedent + import frappe -import frappe.translate -import frappe.modules.patch_handler import frappe.model.sync -from frappe.utils.fixtures import sync_fixtures +import frappe.modules.patch_handler +import frappe.translate +from frappe.cache_manager import clear_global_cache +from frappe.core.doctype.language.language import sync_languages +from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs +from frappe.database.schema import add_column +from frappe.desk.notifications import clear_notifications +from frappe.modules.patch_handler import PatchType +from frappe.modules.utils import sync_customizations +from frappe.search.website_search import build_index_for_all_routes from frappe.utils.connections import check_connection from frappe.utils.dashboard import sync_dashboards -from frappe.cache_manager import clear_global_cache -from frappe.desk.notifications import clear_notifications +from frappe.utils.fixtures import sync_fixtures from frappe.website.utils import clear_website_cache -from frappe.core.doctype.language.language import sync_languages -from frappe.modules.utils import sync_customizations -from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs -from frappe.search.website_search import build_index_for_all_routes -from frappe.database.schema import add_column -from frappe.modules.patch_handler import PatchType + +BENCH_START_MESSAGE = dedent( + """ + Cannot run bench migrate without the services running. + If you are running bench in development mode, make sure that bench is running: + + $ bench start + + Otherwise, check the server logs and ensure that all the required services are running. + """ +) +def atomic(method): + def wrapper(*args, **kwargs): + try: + ret = method(*args, **kwargs) + frappe.db.commit() + return ret + except Exception: + frappe.db.rollback() + raise -def migrate(verbose=True, skip_failing=False, skip_search_index=False): - '''Migrate all apps to the current version, will: + return wrapper + + +class SiteMigration: + """Migrate all apps to the current version, will: - run before migrate hooks - run patches - sync doctypes (schema) @@ -35,70 +59,117 @@ def migrate(verbose=True, skip_failing=False, skip_search_index=False): - sync languages - sync web pages (from /www) - run after migrate hooks - ''' + """ - service_status = check_connection(redis_services=["redis_cache"]) - if False in service_status.values(): - for service in service_status: - if not service_status.get(service, True): - print("{} service is not running.".format(service)) - print("""Cannot run bench migrate without the services running. -If you are running bench in development mode, make sure that bench is running: + def __init__(self, skip_failing: bool = False, skip_search_index: bool = False) -> None: + self.skip_failing = skip_failing + self.skip_search_index = skip_search_index -$ bench start - -Otherwise, check the server logs and ensure that all the required services are running.""") - sys.exit(1) - - touched_tables_file = frappe.get_site_path('touched_tables.json') - if os.path.exists(touched_tables_file): - os.remove(touched_tables_file) - - try: - add_column(doctype="DocType", column_name="migration_hash", fieldtype="Data") + def setUp(self): + """Complete setup required for site migration + """ frappe.flags.touched_tables = set() - frappe.flags.in_migrate = True - + self.touched_tables_file = frappe.get_site_path("touched_tables.json") + add_column(doctype="DocType", column_name="migration_hash", fieldtype="Data") clear_global_cache() + if os.path.exists(self.touched_tables_file): + os.remove(self.touched_tables_file) + + frappe.flags.in_migrate = True + + def tearDown(self): + """Run operations that should be run post schema updation processes + This should be executed irrespective of outcome + """ + frappe.translate.clear_cache() + clear_website_cache() + clear_notifications() + + with open(self.touched_tables_file, "w") as f: + json.dump(list(frappe.flags.touched_tables), f, sort_keys=True, indent=4) + + if not self.skip_search_index: + print(f"Building search index for {frappe.local.site}") + build_index_for_all_routes() + + frappe.publish_realtime("version-update") + frappe.flags.touched_tables.clear() + frappe.flags.in_migrate = False + + @atomic + def pre_schema_updates(self): + """Executes `before_migrate` hooks + """ for app in frappe.get_installed_apps(): - for fn in frappe.get_hooks('before_migrate', app_name=app): + for fn in frappe.get_hooks("before_migrate", app_name=app): frappe.get_attr(fn)() - frappe.modules.patch_handler.run_all(skip_failing=skip_failing, patch_type=PatchType.pre_model_sync) + @atomic + def run_schema_updates(self): + """Run patches as defined in patches.txt, sync schema changes as defined in the {doctype}.json files + """ + frappe.modules.patch_handler.run_all(skip_failing=self.skip_failing, patch_type=PatchType.pre_model_sync) frappe.model.sync.sync_all() - frappe.modules.patch_handler.run_all(skip_failing=skip_failing, patch_type=PatchType.post_model_sync) - frappe.translate.clear_cache() + frappe.modules.patch_handler.run_all(skip_failing=self.skip_failing, patch_type=PatchType.post_model_sync) + + @atomic + def post_schema_updates(self): + """Execute pending migration tasks post patches execution & schema sync + This includes: + * Sync `Scheduled Job Type` and scheduler events defined in hooks + * Sync fixtures & custom scripts + * Sync in-Desk Module Dashboards + * Sync customizations: Custom Fields, Property Setters, Custom Permissions + * Sync Frappe's internal language master + * Sync Portal Menu Items + * Sync Installed Applications Version History + * Execute `after_migrate` hooks + """ sync_jobs() sync_fixtures() sync_dashboards() sync_customizations() sync_languages() - frappe.get_doc('Portal Settings', 'Portal Settings').sync_menu() - - # syncs static files - clear_website_cache() - - # updating installed applications data - frappe.get_single('Installed Applications').update_versions() + frappe.get_single("Portal Settings").sync_menu() + frappe.get_single("Installed Applications").update_versions() for app in frappe.get_installed_apps(): - for fn in frappe.get_hooks('after_migrate', app_name=app): + for fn in frappe.get_hooks("after_migrate", app_name=app): frappe.get_attr(fn)() - if not skip_search_index: - # Run this last as it updates the current session - print('Building search index for {}'.format(frappe.local.site)) - build_index_for_all_routes() + def required_services_running(self) -> bool: + """Returns True if all required services are running. Returns False and prints + instructions to stdout when required services are not available. + """ + service_status = check_connection(redis_services=["redis_cache"]) + are_services_running = all(service_status.values()) - frappe.db.commit() + if not are_services_running: + for service in service_status: + if not service_status.get(service, True): + print(f"Service {service} is not running.") + print(BENCH_START_MESSAGE) - clear_notifications() + return are_services_running - frappe.publish_realtime("version-update") - frappe.flags.in_migrate = False - finally: - with open(touched_tables_file, 'w') as f: - json.dump(list(frappe.flags.touched_tables), f, sort_keys=True, indent=4) - frappe.flags.touched_tables.clear() + def run(self, site: str): + """Run Migrate operation on site specified. This method initializes + and destroys connections to the site database. + """ + if not self.required_services_running(): + raise SystemExit(1) + + if site: + frappe.init(site=site) + frappe.connect() + + self.setUp() + try: + self.pre_schema_updates() + self.run_schema_updates() + finally: + self.post_schema_updates() + self.tearDown() + frappe.destroy() From 74c56c2e6362fe32d745eea2506fe03ac0277b28 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 3 Feb 2022 16:58:29 +0530 Subject: [PATCH 09/71] test: Add test (+ suite) for site migration --- frappe/tests/test_commands.py | 88 +++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index 408d644042..18fd3d636f 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -3,26 +3,35 @@ # imports - standard imports import gzip +import importlib import json import os import shlex import shutil import subprocess -from typing import List import unittest +from contextlib import contextmanager +from functools import wraps from glob import glob +from typing import List from unittest.case import skipIf +from unittest.mock import patch + +# imports - third party imports +import click +from click.testing import CliRunner +from click import Command # imports - module imports import frappe +import frappe.commands.site import frappe.recorder from frappe.installer import add_to_installed_apps, remove_app from frappe.utils import add_to_date, get_bench_path, get_bench_relative_path, now from frappe.utils.backups import fetch_latest_backups -# imports - third party imports -import click - +TEST_SITE = "commands-site.test" +CLI_CONTEXT = frappe._dict(sites=[TEST_SITE]) def clean(value) -> str: """Strips and converts bytes to str @@ -76,7 +85,49 @@ def exists_in_backup(doctypes: List, file: os.PathLike) -> bool: return len(missing_doctypes) == 0 +def restore_locals(f): + @wraps(f) + def decorated_function(*args, **kwargs): + pre_site = frappe.local.site + pre_flags = frappe.local.flags.copy() + pre_db = frappe.local.db + + ret = f(*args, **kwargs) + + post_site = getattr(frappe.local, "site", None) + if not post_site or post_site != pre_site: + frappe.init(site=pre_site) + frappe.local.db = pre_db + frappe.local.flags = pre_flags + return ret + return decorated_function + + +def pass_test_context(f): + @wraps(f) + def decorated_function(*args, **kwargs): + return f(CLI_CONTEXT, *args, **kwargs) + return decorated_function + + +@contextmanager +def cli(cmd: Command): + patch_ctx = patch("frappe.commands.pass_context", pass_test_context) + _module = cmd.callback.__module__ + _cmd = cmd.callback.__qualname__ + + patch_ctx.start() + importlib.reload(frappe.get_module(_module)) + click_cmd = frappe.get_attr(f"{_module}.{_cmd}") + + try: + yield CliRunner().invoke(click_cmd) + finally: + patch_ctx.stop() + + class BaseTestCommands(unittest.TestCase): + @classmethod def execute(self, command, kwargs=None): site = {"site": frappe.local.site} cmd_input = None @@ -585,3 +636,32 @@ class TestRemoveApp(unittest.TestCase): # nothing to assert, if this fails rest of the test suite will crumble. remove_app("frappe", dry_run=True, yes=True, no_backup=True) + + +class TestSiteMigration(BaseTestCommands): + @classmethod + def setUpClass(cls) -> None: + cmd_config = { + "test_site": TEST_SITE, + "admin_password": frappe.conf.admin_password, + "root_login": frappe.conf.root_login, + "root_password": frappe.conf.root_password, + "db_type": frappe.conf.db_type, + } + + if not os.path.exists( + os.path.join(TEST_SITE, "site_config.json") + ): + cls.execute( + "bench new-site {test_site} --admin-password {admin_password} --db-type" + " {db_type}", + cmd_config, + ) + return super().setUpClass() + + @restore_locals + def test_migrate_cli(self): + with cli(frappe.commands.site.migrate) as result: + self.assertTrue(TEST_SITE in result.stdout) + self.assertEqual(result.exit_code, 0) + self.assertEqual(result.exception, None) From d18f4e717fea0bc558ccaa484caa0304da525fce Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 3 Feb 2022 16:58:59 +0530 Subject: [PATCH 10/71] fix(test): Use standard test site for cli invokations --- frappe/tests/test_commands.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index 18fd3d636f..2efea7d7f9 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -194,35 +194,35 @@ class TestCommands(BaseTestCommands): "root_password": frappe.conf.root_password, "db_type": frappe.conf.db_type, } - site_data = {"another_site": f"{frappe.local.site}-restore.test", **global_config} + site_data = {"test_site": TEST_SITE, **global_config} for key, value in global_config.items(): if value: self.execute(f"bench set-config {key} {value} -g") self.execute( - "bench new-site {another_site} --admin-password {admin_password} --db-type" + "bench new-site {test_site} --admin-password {admin_password} --db-type" " {db_type}", site_data, ) # test 1: bench restore from full backup - self.execute("bench --site {another_site} backup --ignore-backup-conf", site_data) + self.execute("bench --site {test_site} backup --ignore-backup-conf", site_data) self.execute( - "bench --site {another_site} execute frappe.utils.backups.fetch_latest_backups", + "bench --site {test_site} execute frappe.utils.backups.fetch_latest_backups", site_data, ) site_data.update({"database": json.loads(self.stdout)["database"]}) - self.execute("bench --site {another_site} restore {database}", site_data) + self.execute("bench --site {test_site} restore {database}", site_data) # test 2: restore from partial backup - self.execute("bench --site {another_site} backup --exclude 'ToDo'", site_data) + self.execute("bench --site {test_site} backup --exclude 'ToDo'", site_data) site_data.update({"kw": "\"{'partial':True}\""}) self.execute( - "bench --site {another_site} execute" + "bench --site {test_site} execute" " frappe.utils.backups.fetch_latest_backups --kwargs {kw}", site_data, ) site_data.update({"database": json.loads(self.stdout)["database"]}) - self.execute("bench --site {another_site} restore {database}", site_data) + self.execute("bench --site {test_site} restore {database}", site_data) self.assertEqual(self.returncode, 1) def test_partial_restore(self): From f343e77ecf132b66cbc245c93a4f8ed3ea7cfd38 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 4 Feb 2022 12:47:58 +0530 Subject: [PATCH 11/71] fix(CliRunner): Test suite for Cli * Manage states * Add formatting on test failures * Patch pass_context --- frappe/tests/test_commands.py | 133 ++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 56 deletions(-) diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index 2efea7d7f9..173b2ebb13 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -13,26 +13,29 @@ import unittest from contextlib import contextmanager from functools import wraps from glob import glob -from typing import List +from typing import List, Optional from unittest.case import skipIf from unittest.mock import patch # imports - third party imports import click -from click.testing import CliRunner +from click.testing import CliRunner, Result from click import Command # imports - module imports import frappe import frappe.commands.site +import frappe.commands.utils import frappe.recorder from frappe.installer import add_to_installed_apps, remove_app from frappe.utils import add_to_date, get_bench_path, get_bench_relative_path, now from frappe.utils.backups import fetch_latest_backups -TEST_SITE = "commands-site.test" +_result: Optional[Result] = None +TEST_SITE = "commands-site-O4PN2QKA.test" # added random string tag to avoid collisions CLI_CONTEXT = frappe._dict(sites=[TEST_SITE]) + def clean(value) -> str: """Strips and converts bytes to str @@ -85,22 +88,20 @@ def exists_in_backup(doctypes: List, file: os.PathLike) -> bool: return len(missing_doctypes) == 0 -def restore_locals(f): - @wraps(f) - def decorated_function(*args, **kwargs): - pre_site = frappe.local.site - pre_flags = frappe.local.flags.copy() - pre_db = frappe.local.db - - ret = f(*args, **kwargs) +@contextmanager +def maintain_locals(): + pre_site = frappe.local.site + pre_flags = frappe.local.flags.copy() + pre_db = frappe.local.db + try: + yield + finally: post_site = getattr(frappe.local, "site", None) if not post_site or post_site != pre_site: frappe.init(site=pre_site) frappe.local.db = pre_db - frappe.local.flags = pre_flags - return ret - return decorated_function + frappe.local.flags.update(pre_flags) def pass_test_context(f): @@ -111,22 +112,36 @@ def pass_test_context(f): @contextmanager -def cli(cmd: Command): - patch_ctx = patch("frappe.commands.pass_context", pass_test_context) - _module = cmd.callback.__module__ - _cmd = cmd.callback.__qualname__ +def cli(cmd: Command, args: Optional[List] = None): + with maintain_locals(): + global _result - patch_ctx.start() - importlib.reload(frappe.get_module(_module)) - click_cmd = frappe.get_attr(f"{_module}.{_cmd}") + patch_ctx = patch("frappe.commands.pass_context", pass_test_context) + _module = cmd.callback.__module__ + _cmd = cmd.callback.__qualname__ - try: - yield CliRunner().invoke(click_cmd) - finally: - patch_ctx.stop() + __module = importlib.import_module(_module) + patch_ctx.start() + importlib.reload(__module) + click_cmd = getattr(__module, _cmd) + + try: + _result = CliRunner().invoke(click_cmd, args=args) + _result.command = str(cmd) + yield _result + finally: + patch_ctx.stop() + __module = importlib.import_module(_module) + importlib.reload(__module) + importlib.invalidate_caches() class BaseTestCommands(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.setup_test_site() + return super().setUpClass() + @classmethod def execute(self, command, kwargs=None): site = {"site": frappe.local.site} @@ -153,16 +168,48 @@ class BaseTestCommands(unittest.TestCase): self.stderr = clean(self._proc.stderr) self.returncode = clean(self._proc.returncode) + @classmethod + def setup_test_site(cls): + cmd_config = { + "test_site": TEST_SITE, + "admin_password": frappe.conf.admin_password, + "root_login": frappe.conf.root_login, + "root_password": frappe.conf.root_password, + "db_type": frappe.conf.db_type, + } + + if not os.path.exists( + os.path.join(TEST_SITE, "site_config.json") + ): + cls.execute( + "bench new-site {test_site} --admin-password {admin_password} --db-type" + " {db_type}", + cmd_config, + ) + def _formatMessage(self, msg, standardMsg): output = super(BaseTestCommands, self)._formatMessage(msg, standardMsg) + + if not hasattr(self, "command") and _result: + command = _result.command + stdout = _result.stdout_bytes.decode() if _result.stdout_bytes else None + stderr = _result.stderr_bytes.decode() if _result.stderr_bytes else None + returncode = _result.exit_code + else: + command = self.command + stdout = self.stdout + stderr = self.stderr + returncode = self.returncode + cmd_execution_summary = "\n".join([ "-" * 70, "Last Command Execution Summary:", - "Command: {}".format(self.command) if self.command else "", - "Standard Output: {}".format(self.stdout) if self.stdout else "", - "Standard Error: {}".format(self.stderr) if self.stderr else "", - "Return Code: {}".format(self.returncode) if self.returncode else "", + "Command: {}".format(command) if command else "", + "Standard Output: {}".format(stdout) if stdout else "", + "Standard Error: {}".format(stderr) if stderr else "", + "Return Code: {}".format(returncode) if returncode else "", ]).strip() + return "{}\n\n{}".format(output, cmd_execution_summary) @@ -198,11 +245,6 @@ class TestCommands(BaseTestCommands): for key, value in global_config.items(): if value: self.execute(f"bench set-config {key} {value} -g") - self.execute( - "bench new-site {test_site} --admin-password {admin_password} --db-type" - " {db_type}", - site_data, - ) # test 1: bench restore from full backup self.execute("bench --site {test_site} backup --ignore-backup-conf", site_data) @@ -409,7 +451,7 @@ class TestCommands(BaseTestCommands): ) def test_bench_drop_site_should_archive_site(self): # TODO: Make this test postgres compatible - site = 'test_site.localhost' + site = TEST_SITE self.execute( f"bench new-site {site} --force --verbose " @@ -639,27 +681,6 @@ class TestRemoveApp(unittest.TestCase): class TestSiteMigration(BaseTestCommands): - @classmethod - def setUpClass(cls) -> None: - cmd_config = { - "test_site": TEST_SITE, - "admin_password": frappe.conf.admin_password, - "root_login": frappe.conf.root_login, - "root_password": frappe.conf.root_password, - "db_type": frappe.conf.db_type, - } - - if not os.path.exists( - os.path.join(TEST_SITE, "site_config.json") - ): - cls.execute( - "bench new-site {test_site} --admin-password {admin_password} --db-type" - " {db_type}", - cmd_config, - ) - return super().setUpClass() - - @restore_locals def test_migrate_cli(self): with cli(frappe.commands.site.migrate) as result: self.assertTrue(TEST_SITE in result.stdout) From ac17f8dfb1ae85acfc047ef6b6126cb8b6bb8582 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 10 Feb 2022 14:56:35 +0530 Subject: [PATCH 12/71] test: Allow list-apps to throw errors based on site's states --- frappe/tests/test_commands.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index 173b2ebb13..4a1b73e7b0 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -319,7 +319,8 @@ class TestCommands(BaseTestCommands): def test_list_apps(self): # test 1: sanity check for command self.execute("bench --site all list-apps") - self.assertEqual(self.returncode, 0) + self.assertIsNotNone(self.returncode) + self.assertIsInstance(self.stdout or self.stderr, str) # test 2: bare functionality for single site self.execute("bench --site {site} list-apps") From 643eecb0acae8ac498aee278800ebd4f0860c5cb Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 11 Feb 2022 16:33:31 +0530 Subject: [PATCH 13/71] test(fix): Test list-apps for site --- frappe/tests/test_commands.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index 4a1b73e7b0..0b87a5eaa5 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -336,14 +336,12 @@ class TestCommands(BaseTestCommands): self.assertSetEqual(list_apps, installed_apps) # test 3: parse json format - self.execute("bench --site all list-apps --format json") + self.execute("bench --site {site} list-apps --format json") self.assertEqual(self.returncode, 0) self.assertIsInstance(json.loads(self.stdout), dict) - self.execute("bench --site {site} list-apps --format json") - self.assertIsInstance(json.loads(self.stdout), dict) - self.execute("bench --site {site} list-apps -f json") + self.assertEqual(self.returncode, 0) self.assertIsInstance(json.loads(self.stdout), dict) def test_show_config(self): From 804f3941ffac8d635656d643bedc612f0c474a2e Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 11 Feb 2022 18:27:50 +0530 Subject: [PATCH 14/71] test(restore): Skip test_restore ref: * https://github.com/frappe/frappe/runs/5156148762?check_suite_focus=true * https://github.com/frappe/frappe/runs/5156148706?check_suite_focus=true --- frappe/tests/test_commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index 0b87a5eaa5..00888c08a7 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -233,6 +233,7 @@ class TestCommands(BaseTestCommands): self.assertEqual(self.returncode, 0) self.assertEqual(self.stdout[1:-1], frappe.bold(text="DocType")) + @unittest.skip def test_restore(self): # step 0: create a site to run the test on global_config = { From 67df13f77f5c64a7f401e736991c108e6dbbab08 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 11 Feb 2022 19:24:54 +0530 Subject: [PATCH 15/71] test: Add test for asset building command --- frappe/tests/test_commands.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index 00888c08a7..68605444f1 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -686,3 +686,10 @@ class TestSiteMigration(BaseTestCommands): self.assertTrue(TEST_SITE in result.stdout) self.assertEqual(result.exit_code, 0) self.assertEqual(result.exception, None) + + +class TestBenchBuild(BaseTestCommands): + def test_build_assets(self): + with cli(frappe.commands.utils.build) as result: + self.assertEqual(result.exit_code, 0) + self.assertEqual(result.exception, None) From d72aeca18fc65e54301596040c4f889c1637d683 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 11 Feb 2022 20:05:19 +0530 Subject: [PATCH 16/71] chore!: Deprecate frappe.utils.minify * Remove dead code from build.py * Whitespaces & imports style fixes --- frappe/build.py | 149 +++-------------------------- frappe/utils/minify.py | 212 ----------------------------------------- 2 files changed, 15 insertions(+), 346 deletions(-) delete mode 100644 frappe/utils/minify.py diff --git a/frappe/build.py b/frappe/build.py index 6b93b8b93a..7a06ee3a22 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -1,25 +1,21 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE import os -import re -import json import shutil +import re import subprocess -from subprocess import getoutput -from io import StringIO -from tempfile import mkdtemp, mktemp from distutils.spawn import find_executable - -import frappe -from frappe.utils.minify import JavascriptMinify +from subprocess import getoutput +from tempfile import mkdtemp, mktemp +from urllib.parse import urlparse import click import psutil -from urllib.parse import urlparse -from semantic_version import Version from requests import head from requests.exceptions import HTTPError +from semantic_version import Version +import frappe timestamps = {} app_paths = None @@ -32,6 +28,7 @@ class AssetsNotDownloadedError(Exception): class AssetsDontExistError(HTTPError): pass + def download_file(url, prefix): from requests import get @@ -277,12 +274,14 @@ def check_node_executable(): click.echo(f"{warn} Please install yarn using below command and try again.\nnpm install -g yarn") click.echo() + def get_node_env(): node_env = { "NODE_OPTIONS": f"--max_old_space_size={get_safe_max_old_space_size()}" } return node_env + def get_safe_max_old_space_size(): safe_max_old_space_size = 0 try: @@ -296,6 +295,7 @@ def get_safe_max_old_space_size(): return safe_max_old_space_size + def generate_assets_map(): symlinks = {} @@ -344,7 +344,6 @@ def clear_broken_symlinks(): os.remove(path) - def unstrip(message: str) -> str: """Pads input string on the right side until the last available column in the terminal """ @@ -397,94 +396,6 @@ def link_assets_dir(source, target, hard_link=False): symlink(source, target, overwrite=True) -def build(no_compress=False, verbose=False): - for target, sources in get_build_maps().items(): - pack(os.path.join(assets_path, target), sources, no_compress, verbose) - - -def get_build_maps(): - """get all build.jsons with absolute paths""" - # framework js and css files - - build_maps = {} - for app_path in app_paths: - path = os.path.join(app_path, "public", "build.json") - if os.path.exists(path): - with open(path) as f: - try: - for target, sources in (json.loads(f.read() or "{}")).items(): - # update app path - source_paths = [] - for source in sources: - if isinstance(source, list): - s = frappe.get_pymodule_path(source[0], *source[1].split("/")) - else: - s = os.path.join(app_path, source) - source_paths.append(s) - - build_maps[target] = source_paths - except ValueError as e: - print(path) - print("JSON syntax error {0}".format(str(e))) - return build_maps - - -def pack(target, sources, no_compress, verbose): - outtype, outtxt = target.split(".")[-1], "" - jsm = JavascriptMinify() - - for f in sources: - suffix = None - if ":" in f: - f, suffix = f.split(":") - if not os.path.exists(f) or os.path.isdir(f): - print("did not find " + f) - continue - timestamps[f] = os.path.getmtime(f) - try: - with open(f, "r") as sourcefile: - data = str(sourcefile.read(), "utf-8", errors="ignore") - - extn = f.rsplit(".", 1)[1] - - if ( - outtype == "js" - and extn == "js" - and (not no_compress) - and suffix != "concat" - and (".min." not in f) - ): - tmpin, tmpout = StringIO(data.encode("utf-8")), StringIO() - jsm.minify(tmpin, tmpout) - minified = tmpout.getvalue() - if minified: - outtxt += str(minified or "", "utf-8").strip("\n") + ";" - - if verbose: - print("{0}: {1}k".format(f, int(len(minified) / 1024))) - elif outtype == "js" and extn == "html": - # add to frappe.templates - outtxt += html_to_js_template(f, data) - else: - outtxt += "\n/*\n *\t%s\n */" % f - outtxt += "\n" + data + "\n" - - except Exception: - print("--Error in:" + f + "--") - print(frappe.get_traceback()) - - with open(target, "w") as f: - f.write(outtxt.encode("utf-8")) - - print("Wrote %s - %sk" % (target, str(int(os.path.getsize(target) / 1024)))) - - -def html_to_js_template(path, content): - """returns HTML template content as Javascript code, adding it to `frappe.templates`""" - return """frappe.templates["{key}"] = '{content}';\n""".format( - key=path.rsplit("/", 1)[-1][:-5], content=scrub_html_template(content)) - - def scrub_html_template(content): """Returns HTML content with removed whitespace and comments""" # remove whitespace to a single space @@ -496,37 +407,7 @@ def scrub_html_template(content): return content.replace("'", "\'") -def files_dirty(): - for target, sources in get_build_maps().items(): - for f in sources: - if ":" in f: - f, suffix = f.split(":") - if not os.path.exists(f) or os.path.isdir(f): - continue - if os.path.getmtime(f) != timestamps.get(f): - print(f + " dirty") - return True - else: - return False - - -def compile_less(): - if not find_executable("lessc"): - return - - for path in app_paths: - less_path = os.path.join(path, "public", "less") - if os.path.exists(less_path): - for fname in os.listdir(less_path): - if fname.endswith(".less") and fname != "variables.less": - fpath = os.path.join(less_path, fname) - mtime = os.path.getmtime(fpath) - if fpath in timestamps and mtime == timestamps[fpath]: - continue - - timestamps[fpath] = mtime - - print("compiling {0}".format(fpath)) - - css_path = os.path.join(path, "public", "css", fname.rsplit(".", 1)[0] + ".css") - os.system("lessc {0} > {1}".format(fpath, css_path)) +def html_to_js_template(path, content): + """returns HTML template content as Javascript code, adding it to `frappe.templates`""" + return """frappe.templates["{key}"] = '{content}';\n""".format( + key=path.rsplit("/", 1)[-1][:-5], content=scrub_html_template(content)) diff --git a/frappe/utils/minify.py b/frappe/utils/minify.py deleted file mode 100644 index 634aa11038..0000000000 --- a/frappe/utils/minify.py +++ /dev/null @@ -1,212 +0,0 @@ - -# This code is original from jsmin by Douglas Crockford, it was translated to -# Python by Baruch Even. The original code had the following copyright and -# license. -# -# /* jsmin.c -# 2007-05-22 -# -# Copyright (c) 2002 Douglas Crockford (www.crockford.com) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of -# this software and associated documentation files (the "Software"), to deal in -# the Software without restriction, including without limitation the rights to -# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -# of the Software, and to permit persons to whom the Software is furnished to do -# so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# The Software shall be used for Good, not Evil. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# */ - -from io import StringIO - -def jsmin(js): - ins = StringIO(js) - outs = StringIO() - JavascriptMinify().minify(ins, outs) - str = outs.getvalue() - if len(str) > 0 and str[0] == '\n': - str = str[1:] - return str - -def isAlphanum(c): - """return true if the character is a letter, digit, underscore, - dollar sign, or non-ASCII character. - """ - return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or - (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126)); - -class UnterminatedComment(Exception): - pass - -class UnterminatedStringLiteral(Exception): - pass - -class UnterminatedRegularExpression(Exception): - pass - -class JavascriptMinify(object): - - def _outA(self): - self.outstream.write(self.theA) - def _outB(self): - self.outstream.write(self.theB) - - def _get(self): - """return the next character from stdin. Watch out for lookahead. If - the character is a control character, translate it to a space or - linefeed. - """ - c = self.theLookahead - self.theLookahead = None - if c is None: - c = self.instream.read(1) - if c >= ' ' or c == '\n': - return c - if c == '': # EOF - return '\000' - if c == '\r': - return '\n' - return ' ' - - def _peek(self): - self.theLookahead = self._get() - return self.theLookahead - - def _next(self): - """get the next character, excluding comments. peek() is used to see - if an unescaped '/' is followed by a '/' or '*'. - """ - c = self._get() - if c == '/' and self.theA != '\\': - p = self._peek() - if p == '/': - c = self._get() - while c > '\n': - c = self._get() - return c - if p == '*': - c = self._get() - while 1: - c = self._get() - if c == '*': - if self._peek() == '/': - self._get() - return ' ' - if c == '\000': - raise UnterminatedComment() - - return c - - def _action(self, action): - """do something! What you do is determined by the argument: - 1 Output A. Copy B to A. Get the next B. - 2 Copy B to A. Get the next B. (Delete A). - 3 Get the next B. (Delete B). - action treats a string as a single character. Wow! - action recognizes a regular expression if it is preceded by ( or , or =. - """ - if action <= 1: - self._outA() - - if action <= 2: - self.theA = self.theB - if self.theA == "'" or self.theA == '"': - while 1: - self._outA() - self.theA = self._get() - if self.theA == self.theB: - break - if self.theA <= '\n': - raise UnterminatedStringLiteral() - if self.theA == '\\': - self._outA() - self.theA = self._get() - - - if action <= 3: - self.theB = self._next() - if self.theB == '/' and (self.theA == '(' or self.theA == ',' or - self.theA == '=' or self.theA == ':' or - self.theA == '[' or self.theA == '?' or - self.theA == '!' or self.theA == '&' or - self.theA == '|' or self.theA == ';' or - self.theA == '{' or self.theA == '}' or - self.theA == '\n'): - self._outA() - self._outB() - while 1: - self.theA = self._get() - if self.theA == '/': - break - elif self.theA == '\\': - self._outA() - self.theA = self._get() - elif self.theA <= '\n': - raise UnterminatedRegularExpression() - self._outA() - self.theB = self._next() - - - def _jsmin(self): - """Copy the input to the output, deleting the characters which are - insignificant to JavaScript. Comments will be removed. Tabs will be - replaced with spaces. Carriage returns will be replaced with linefeeds. - Most spaces and linefeeds will be removed. - """ - self.theA = '\n' - self._action(3) - - while self.theA != '\000': - if self.theA == ' ': - if isAlphanum(self.theB): - self._action(1) - else: - self._action(2) - elif self.theA == '\n': - if self.theB in ['{', '[', '(', '+', '-']: - self._action(1) - elif self.theB == ' ': - self._action(3) - else: - if isAlphanum(self.theB): - self._action(1) - else: - self._action(2) - else: - if self.theB == ' ': - if isAlphanum(self.theA): - self._action(1) - else: - self._action(3) - elif self.theB == '\n': - if self.theA in ['}', ']', ')', '+', '-', '"', '\'']: - self._action(1) - else: - if isAlphanum(self.theA): - self._action(1) - else: - self._action(3) - else: - self._action(1) - - def minify(self, instream, outstream): - self.instream = instream - self.outstream = outstream - self.theA = '\n' - self.theB = None - self.theLookahead = None - - self._jsmin() - self.instream.close() From 70c27e424dc00ea3742c104a15469005eb65990b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 11 Feb 2022 16:26:23 +0530 Subject: [PATCH 17/71] fix: consider parenttype and parent as std child fields --- frappe/public/js/frappe/form/form.js | 4 +++- frappe/public/js/frappe/form/grid.js | 3 ++- frappe/public/js/frappe/model/meta.js | 2 +- frappe/public/js/frappe/model/model.js | 4 +++- frappe/public/js/frappe/ui/filters/filter_list.js | 4 +++- frappe/public/js/frappe/views/reports/report_view.js | 3 ++- 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index c0c7ce8b4e..d5adf6e7bd 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1508,7 +1508,9 @@ frappe.ui.form.Form = class FrappeForm { // update child doc opts.child = locals[opts.child.doctype][opts.child.name]; - var std_field_list = ["doctype"].concat(frappe.model.std_fields_list); + var std_field_list = ["doctype"] + .concat(frappe.model.std_fields_list) + .concat(frappe.model.child_table_field_list); for (var key in r.message) { if (std_field_list.indexOf(key)===-1) { opts.child[key] = r.message[key]; diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 8b615f3c59..e3361ce533 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -747,7 +747,8 @@ export default class Grid { var df = this.visible_columns[i][0]; var colsize = this.visible_columns[i][1]; if (colsize > 1 && colsize < 11 - && !in_list(frappe.model.std_fields_list, df.fieldname)) { + && !in_list(frappe.model.std_fields_list, df.fieldname) + && !in_list(frappe.model.child_table_field_list, df.fieldname)) { if (passes < 3 && ["Int", "Currency", "Float", "Check", "Percent"].indexOf(df.fieldtype) !== -1) { // don't increase col size of these fields in first 3 passes diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index 3c9ddc4d96..03750f7b92 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -152,7 +152,7 @@ $.extend(frappe.meta, { out = doctype; } else { frappe.meta.get_table_fields(doctype).every(function(d) { - if(frappe.meta.has_field(d.options, key)) { + if(frappe.meta.has_field(d.options, key) || in_list(frappe.model.child_table_field_list, key)) { out = d.options; return false; } diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index 89e029ffb1..7dabb92059 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -12,6 +12,8 @@ $.extend(frappe.model, { std_fields_list: ['name', 'owner', 'creation', 'modified', 'modified_by', '_user_tags', '_comments', '_assign', '_liked_by', 'docstatus', 'idx'], + child_table_field_list: ['parent', 'parenttype', 'parentfield'], + core_doctypes_list: ['DocType', 'DocField', 'DocPerm', 'User', 'Role', 'Has Role', 'Page', 'Module Def', 'Print Format', 'Report', 'Customize Form', 'Customize Form Field', 'Property Setter', 'Custom Field', 'Client Script'], @@ -83,7 +85,7 @@ $.extend(frappe.model, { }, is_non_std_field: function(fieldname) { - return !frappe.model.std_fields_list.includes(fieldname); + return ![...frappe.model.std_fields_list, ...frappe.model.child_table_field_list].includes(fieldname); }, get_std_field: function(fieldname, ignore=false) { diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index 18499a3b7e..33b7f5544b 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -170,7 +170,8 @@ frappe.ui.FilterGroup = class { validate_args(doctype, fieldname) { if (doctype && fieldname && !frappe.meta.has_field(doctype, fieldname) - && !frappe.model.std_fields_list.includes(fieldname)) { + && !frappe.model.std_fields_list.includes(fieldname) + && !frappe.model.child_table_field_list.includes(fieldname)) { frappe.msgprint({ message: __('Invalid filter: {0}', [fieldname.bold()]), @@ -324,6 +325,7 @@ frappe.ui.FilterGroup = class { } add_filters_to_filter_group(filters) { + console.log(filters); if (filters.length) { this.toggle_empty_filters(false); filters.forEach((filter) => { diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 23e415ed3e..5aa32c1b87 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -646,7 +646,8 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { && !df.read_only && !df.hidden // not a standard field i.e., owner, modified_by, etc. - && !frappe.model.std_fields_list.includes(df.fieldname)) + && !frappe.model.std_fields_list.includes(df.fieldname) + && !frappe.model.child_table_field_list.includes(df.fieldname)) return true; return false; } From 7229fe3b1c2701d1ad18009c96daf6f1cf01ca77 Mon Sep 17 00:00:00 2001 From: phot0n Date: Sun, 13 Feb 2022 13:11:24 +0530 Subject: [PATCH 18/71] chore: fix sider --- frappe/public/js/frappe/model/meta.js | 4 ++-- frappe/public/js/frappe/ui/filters/filter_list.js | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index 03750f7b92..4a7295ed4e 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -144,7 +144,7 @@ $.extend(frappe.meta, { get_doctype_for_field: function(doctype, key) { var out = null; - if(in_list(frappe.model.std_fields_list, key)) { + if (in_list(frappe.model.std_fields_list, key)) { // standard out = doctype; } else if(frappe.meta.has_field(doctype, key)) { @@ -152,7 +152,7 @@ $.extend(frappe.meta, { out = doctype; } else { frappe.meta.get_table_fields(doctype).every(function(d) { - if(frappe.meta.has_field(d.options, key) || in_list(frappe.model.child_table_field_list, key)) { + if (frappe.meta.has_field(d.options, key) || in_list(frappe.model.child_table_field_list, key)) { out = d.options; return false; } diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index 33b7f5544b..6617b2d3c5 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -325,7 +325,6 @@ frappe.ui.FilterGroup = class { } add_filters_to_filter_group(filters) { - console.log(filters); if (filters.length) { this.toggle_empty_filters(false); filters.forEach((filter) => { From f57d93d0d50685f369c2e40c8612b96eaf371fb9 Mon Sep 17 00:00:00 2001 From: phot0n Date: Sun, 13 Feb 2022 13:36:12 +0530 Subject: [PATCH 19/71] chore: use frappe.model.is_non_std_field --- frappe/public/js/frappe/form/grid.js | 3 +-- frappe/public/js/frappe/ui/filters/filter_list.js | 3 +-- frappe/public/js/frappe/views/reports/report_view.js | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index e3361ce533..c17ca06003 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -747,8 +747,7 @@ export default class Grid { var df = this.visible_columns[i][0]; var colsize = this.visible_columns[i][1]; if (colsize > 1 && colsize < 11 - && !in_list(frappe.model.std_fields_list, df.fieldname) - && !in_list(frappe.model.child_table_field_list, df.fieldname)) { + && frappe.model.is_non_std_field(df.fieldname)) { if (passes < 3 && ["Int", "Currency", "Float", "Check", "Percent"].indexOf(df.fieldtype) !== -1) { // don't increase col size of these fields in first 3 passes diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index 6617b2d3c5..1377a6ff13 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -170,8 +170,7 @@ frappe.ui.FilterGroup = class { validate_args(doctype, fieldname) { if (doctype && fieldname && !frappe.meta.has_field(doctype, fieldname) - && !frappe.model.std_fields_list.includes(fieldname) - && !frappe.model.child_table_field_list.includes(fieldname)) { + && frappe.model.is_non_std_field(fieldname)) { frappe.msgprint({ message: __('Invalid filter: {0}', [fieldname.bold()]), diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 5aa32c1b87..44d82988e6 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -646,8 +646,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { && !df.read_only && !df.hidden // not a standard field i.e., owner, modified_by, etc. - && !frappe.model.std_fields_list.includes(df.fieldname) - && !frappe.model.child_table_field_list.includes(df.fieldname)) + && frappe.model.is_non_std_field(df.fieldname)) return true; return false; } From 857209d630bd4af96e3e218b56853d29e628f731 Mon Sep 17 00:00:00 2001 From: phot0n Date: Sun, 13 Feb 2022 23:24:49 +0530 Subject: [PATCH 20/71] chore: remove unrelated comment --- frappe/model/base_document.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 307d95e84b..6c67028368 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -105,9 +105,6 @@ class BaseDocument(object): }) """ - # QUESTION: why do we need the 1st for loop? - # we're essentially setting the values in d, in the 2nd for loop (?) - # first set default field values of base document for key in default_fields: if key in d: From 50021ab22f4cac23e2c8ec9838a468385d9f11cd Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 14 Feb 2022 13:22:09 +0530 Subject: [PATCH 21/71] fix: depends_on not working with tabs --- frappe/public/js/frappe/form/layout.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 0de6b1db0d..f7219282c6 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -552,19 +552,21 @@ frappe.ui.form.Layout = class Layout { // build dependants' dictionary let has_dep = false; - for (let fkey in this.fields_list) { - let f = this.fields_list[fkey]; - f.dependencies_clear = true; + const fields = this.fields_list.concat(this.tabs); + + for (let fkey in fields) { + let f = fields[fkey]; if (f.df.depends_on || f.df.mandatory_depends_on || f.df.read_only_depends_on) { has_dep = true; + break; } } if (!has_dep) return; // show / hide based on values - for (let i = this.fields_list.length - 1; i >= 0; i--) { - let f = this.fields_list[i]; + for (let i = fields.length - 1; i >= 0; i--) { + let f = fields[i]; f.guardian_has_value = true; if (f.df.depends_on) { // evaluate guardian From d1846b6dcf19cc86821323f67aa321a3d1c9926d Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Tue, 15 Feb 2022 12:20:56 +0530 Subject: [PATCH 22/71] fix: add new row with linked field only if table is mandatory --- 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 748ebda772..1fa5d6192d 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1684,7 +1684,7 @@ frappe.ui.form.Form = class FrappeForm { new_doc[df.fieldname] = me.doc.name; } else if (['Link', 'Dynamic Link'].includes(df.fieldtype) && me.doc[df.fieldname]) { new_doc[df.fieldname] = me.doc[df.fieldname]; - } else if (df.fieldtype === 'Table' && df.options) { + } else if (df.fieldtype === 'Table' && df.options && df.reqd) { let row = new_doc[df.fieldname][0]; me.set_link_field(df.options, row); } From 39d65c5211a30c413920ea43c4a607248c7e36b9 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Tue, 15 Feb 2022 12:22:14 +0530 Subject: [PATCH 23/71] test: UI test to check if linked field is populated in child table --- cypress/fixtures/child_table_doctype.js | 30 +++++++++++++ cypress/fixtures/doctype_to_link.js | 45 +++++++++++++++++++ cypress/fixtures/doctype_with_child_table.js | 46 ++++++++++++++++++++ cypress/integration/dashboard_links.js | 24 ++++++++++ frappe/tests/ui_test_helpers.py | 14 ++++++ 5 files changed, 159 insertions(+) create mode 100644 cypress/fixtures/child_table_doctype.js create mode 100644 cypress/fixtures/doctype_to_link.js create mode 100644 cypress/fixtures/doctype_with_child_table.js diff --git a/cypress/fixtures/child_table_doctype.js b/cypress/fixtures/child_table_doctype.js new file mode 100644 index 0000000000..697b0b5632 --- /dev/null +++ b/cypress/fixtures/child_table_doctype.js @@ -0,0 +1,30 @@ +export default { + name: "Child Table Doctype", + actions: [], + custom: 1, + autoname: "field:title", + creation: "2022-02-09 20:15:21.242213", + doctype: "DocType", + editable_grid: 1, + engine: "InnoDB", + fields: [ + { + fieldname: "title", + fieldtype: "Data", + in_list_view: 1, + label: "Title", + unique: 1 + } + ], + links: [], + istable: 1, + modified: "2022-02-10 12:03:12.603763", + modified_by: "Administrator", + module: "Custom", + naming_rule: "By fieldname", + owner: "Administrator", + permissions: [], + sort_field: 'modified', + sort_order: 'ASC', + track_changes: 1 +} \ No newline at end of file diff --git a/cypress/fixtures/doctype_to_link.js b/cypress/fixtures/doctype_to_link.js new file mode 100644 index 0000000000..c12cf8ca55 --- /dev/null +++ b/cypress/fixtures/doctype_to_link.js @@ -0,0 +1,45 @@ +export default { + name: "Doctype to Link", + actions: [], + custom: 1, + naming_rule: "By fieldname", + autoname: "field:title", + creation: "2022-02-09 20:15:21.242213", + doctype: "DocType", + editable_grid: 1, + engine: "InnoDB", + fields: [ + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "unique": 1 + } + ], + links: [ + { + "group": "Child Doctype", + "link_doctype": "Doctype With Child Table", + "link_fieldname": "title" + } + ], + modified: "2022-02-10 12:03:12.603763", + modified_by: "Administrator", + module: "Custom", + owner: "Administrator", + permissions: [ + { + create: 1, + delete: 1, + email: 1, + print: 1, + read: 1, + role: 'System Manager', + share: 1, + write: 1 + } + ], + sort_field: 'modified', + sort_order: 'ASC', + track_changes: 1 +} \ No newline at end of file diff --git a/cypress/fixtures/doctype_with_child_table.js b/cypress/fixtures/doctype_with_child_table.js new file mode 100644 index 0000000000..bc96badab2 --- /dev/null +++ b/cypress/fixtures/doctype_with_child_table.js @@ -0,0 +1,46 @@ +export default { + name: "Doctype With Child Table", + actions: [], + custom: 1, + autoname: "field:title", + creation: "2022-02-09 20:15:21.242213", + doctype: "DocType", + editable_grid: 1, + engine: "InnoDB", + fields: [ + { + fieldname: "title", + fieldtype: "Data", + label: "Title", + unique: 1 + }, + { + fieldname: "child_table", + fieldtype: "Table", + label: "Child Table", + options: "Child Table Doctype", + reqd: 1 + } + ], + links: [], + modified: "2022-02-10 12:03:12.603763", + modified_by: "Administrator", + module: "Custom", + naming_rule: "By fieldname", + owner: "Administrator", + permissions: [ + { + create: 1, + delete: 1, + email: 1, + print: 1, + read: 1, + role: 'System Manager', + share: 1, + write: 1 + } + ], + sort_field: 'modified', + sort_order: 'ASC', + track_changes: 1 +} diff --git a/cypress/integration/dashboard_links.js b/cypress/integration/dashboard_links.js index 16ffd41cf4..93d10cf1fd 100644 --- a/cypress/integration/dashboard_links.js +++ b/cypress/integration/dashboard_links.js @@ -1,7 +1,21 @@ +import doctype_with_child_table from '../fixtures/doctype_with_child_table'; +import child_table_doctype from '../fixtures/child_table_doctype'; +import doctype_to_link from '../fixtures/doctype_to_link'; +const doctype_to_link_name = doctype_to_link.name; +const child_table_doctype_name = child_table_doctype.name; + context('Dashboard links', () => { before(() => { cy.visit('/login'); cy.login(); + cy.insert_doc('DocType', child_table_doctype, true); + cy.insert_doc('DocType', doctype_with_child_table, true); + cy.insert_doc('DocType', doctype_to_link, true); + return cy.window().its('frappe').then(frappe => { + return frappe.xcall("frappe.tests.ui_test_helpers.update_child_table", { + name: child_table_doctype_name + }); + }); }); it('Adding a new contact, checking for the counter on the dashboard and deleting the created contact', () => { @@ -62,4 +76,14 @@ context('Dashboard links', () => { cy.findByText('Website Analytics'); }); }); + + it('check if child table is populated with linked field on creation from dashboard link', () => { + cy.new_form(doctype_to_link_name); + cy.fill_field("title", "Test Linking"); + cy.findByRole("button", {name: "Save"}).click(); + + cy.get('.document-link .btn-new').click(); + cy.get('.frappe-control[data-fieldname="child_table"] .rows .data-row .col[data-fieldname="doctype_to_link"]') + .should('contain.text', 'Test Linking'); + }); }); diff --git a/frappe/tests/ui_test_helpers.py b/frappe/tests/ui_test_helpers.py index b299df522c..5d3e75f02c 100644 --- a/frappe/tests/ui_test_helpers.py +++ b/frappe/tests/ui_test_helpers.py @@ -256,3 +256,17 @@ def update_webform_to_multistep(): _doc.route = "update-profile-duplicate" _doc.is_standard = False _doc.save() + +@frappe.whitelist() +def update_child_table(name): + doc = frappe.get_doc('DocType', name) + if len(doc.fields) == 1: + doc.append('fields', { + 'fieldname': 'doctype_to_link', + 'fieldtype': 'Link', + 'in_list_view': 1, + 'label': 'Doctype to Link', + 'options': 'Doctype to Link' + }) + + doc.save() \ No newline at end of file From f01de7e5387268b54310b27923ceba33f6cf2d8a Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Tue, 15 Feb 2022 12:28:29 +0530 Subject: [PATCH 24/71] fix(sider): missing semicolons --- cypress/fixtures/child_table_doctype.js | 2 +- cypress/fixtures/doctype_to_link.js | 2 +- cypress/fixtures/doctype_with_child_table.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cypress/fixtures/child_table_doctype.js b/cypress/fixtures/child_table_doctype.js index 697b0b5632..f65e5d1765 100644 --- a/cypress/fixtures/child_table_doctype.js +++ b/cypress/fixtures/child_table_doctype.js @@ -27,4 +27,4 @@ export default { sort_field: 'modified', sort_order: 'ASC', track_changes: 1 -} \ No newline at end of file +}; \ No newline at end of file diff --git a/cypress/fixtures/doctype_to_link.js b/cypress/fixtures/doctype_to_link.js index c12cf8ca55..f5335b1755 100644 --- a/cypress/fixtures/doctype_to_link.js +++ b/cypress/fixtures/doctype_to_link.js @@ -42,4 +42,4 @@ export default { sort_field: 'modified', sort_order: 'ASC', track_changes: 1 -} \ No newline at end of file +}; \ No newline at end of file diff --git a/cypress/fixtures/doctype_with_child_table.js b/cypress/fixtures/doctype_with_child_table.js index bc96badab2..bbb2127448 100644 --- a/cypress/fixtures/doctype_with_child_table.js +++ b/cypress/fixtures/doctype_with_child_table.js @@ -43,4 +43,4 @@ export default { sort_field: 'modified', sort_order: 'ASC', track_changes: 1 -} +}; From 18ba5fcd49d53f9af6d883721fde45a10e243fd1 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 15 Feb 2022 14:10:55 +0530 Subject: [PATCH 25/71] perf: reduce one query in `get_controller` --- frappe/model/base_document.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 18b9212e1d..eb08a9b0ae 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -33,13 +33,12 @@ def get_controller(doctype): module_name, custom = frappe.db.get_value( "DocType", doctype, ("module", "custom"), cache=True - ) or ["Core", False] + ) or ("Core", False) if custom: - if frappe.db.field_exists("DocType", "is_tree"): - is_tree = frappe.db.get_value("DocType", doctype, "is_tree", cache=True) - else: - is_tree = False + is_tree = frappe.db.get_value( + "DocType", doctype, "is_tree", ignore=True, cache=True + ) _class = NestedSet if is_tree else Document else: class_overrides = frappe.get_hooks('override_doctype_class') From 24a750c048b5a0cfbfe5b50bfa19f5a0f2138135 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 16 Feb 2022 10:52:48 +0530 Subject: [PATCH 26/71] chore: remove redundant code from `update_if_missing` --- frappe/model/base_document.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 83d3d70eea..f4a4916a9f 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -120,14 +120,18 @@ class BaseDocument(object): return self def update_if_missing(self, d): + """Set default values for fields without existing values""" if isinstance(d, BaseDocument): d = d.get_valid_dict() - if "doctype" in d: - self.set("doctype", d.get("doctype")) for key, value in d.items(): - # dont_update_if_missing is a list of fieldnames, for which, you don't want to set default value - if (self.get(key) is None) and (value is not None) and (key not in self.dont_update_if_missing): + if ( + value is not None + and self.get(key) is None + # dont_update_if_missing is a list of fieldnames + # for which you don't want to set default value + and key not in self.dont_update_if_missing + ): self.set(key, value) def get_db_value(self, key): From 6d4df45ab0cd094f195a860dfddc5b00bcc8f960 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 16 Feb 2022 13:58:24 +0530 Subject: [PATCH 27/71] revert: unrelated formatting changes --- frappe/public/js/frappe/form/form.js | 196 +++++++++++++-------------- 1 file changed, 98 insertions(+), 98 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 1fa5d6192d..171115b60b 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -44,7 +44,7 @@ frappe.ui.form.Form = class FrappeForm { setup_meta() { this.meta = frappe.get_doc('DocType', this.doctype); - if (this.meta.istable) { + if(this.meta.istable) { this.meta.in_dialog = 1; } @@ -248,7 +248,7 @@ frappe.ui.form.Form = class FrappeForm { // on main doc frappe.model.on(me.doctype, "*", function(fieldname, value, doc) { // set input - if (doc.name === me.docname) { + if(doc.name===me.docname) { me.dirty(); let field = me.fields_dict[fieldname]; @@ -271,7 +271,7 @@ frappe.ui.form.Form = class FrappeForm { // using $.each to preserve df via closure $.each(table_fields, function(i, df) { frappe.model.on(df.options, "*", function(fieldname, value, doc) { - if (doc.parent === me.docname && doc.parentfield === df.fieldname) { + if(doc.parent===me.docname && doc.parentfield===df.fieldname) { me.dirty(); me.fields_dict[df.fieldname].grid.set_value(fieldname, value, doc); return me.script_manager.trigger(fieldname, doc.doctype, doc.name); @@ -282,7 +282,7 @@ frappe.ui.form.Form = class FrappeForm { setup_notify_on_rename() { $(document).on('rename', (ev, dt, old_name, new_name) => { - if (dt == this.doctype) + if (dt==this.doctype) this.rename_notify(dt, old_name, new_name); }); } @@ -299,7 +299,7 @@ frappe.ui.form.Form = class FrappeForm { e.stopPropagation(); e.preventDefault(); - if (me.doc.__islocal) { + if(me.doc.__islocal) { frappe.msgprint(__("Please save before attaching.")); throw "attach error"; } @@ -322,13 +322,13 @@ frappe.ui.form.Form = class FrappeForm { refresh(docname) { var switched = docname ? true : false; - if (docname) { + if(docname) { this.switch_doc(docname); } cur_frm = this; - if (this.docname) { // document to show + if(this.docname) { // document to show this.save_disabled = false; // set the doc this.doc = frappe.get_doc(this.doctype, this.docname); @@ -353,7 +353,7 @@ frappe.ui.form.Form = class FrappeForm { } // do setup - if (!this.setup_done) { + if(!this.setup_done) { this.setup(); } @@ -361,12 +361,12 @@ frappe.ui.form.Form = class FrappeForm { this.trigger_onload(switched); // if print format is shown, refresh the format - // if (this.print_preview.wrapper.is(":visible")) { + // if(this.print_preview.wrapper.is(":visible")) { // this.print_preview.preview(); // } - if (switched) { - if (this.show_print_first && this.doc.docstatus === 1) { + if(switched) { + if(this.show_print_first && this.doc.docstatus===1) { // show print view this.print_doc(); } @@ -374,9 +374,9 @@ frappe.ui.form.Form = class FrappeForm { // set status classes this.$wrapper.removeClass('validated-form') - .toggleClass('editable-form', this.doc.docstatus === 0) - .toggleClass('submitted-form', this.doc.docstatus === 1) - .toggleClass('cancelled-form', this.doc.docstatus === 2); + .toggleClass('editable-form', this.doc.docstatus===0) + .toggleClass('submitted-form', this.doc.docstatus===1) + .toggleClass('cancelled-form', this.doc.docstatus===2); this.show_conflict_message(); } @@ -415,7 +415,7 @@ frappe.ui.form.Form = class FrappeForm { frappe.throw(`Action ${action} not found`); } } - if (action.action_type === 'Server Action') { + if (action.action_type==='Server Action') { return frappe.xcall(action.action, {'doc': this.doc}).then((doc) => { if (doc.doctype) { // document is returned by the method, @@ -430,7 +430,7 @@ frappe.ui.form.Form = class FrappeForm { alert: true }); }); - } else if (action.action_type === 'Route') { + } else if (action.action_type==='Route') { return frappe.set_route(action.action); } } @@ -449,7 +449,7 @@ frappe.ui.form.Form = class FrappeForm { } check_reload() { - if (this.doc && (!this.doc.__unsaved) && this.doc.__last_sync_on && + if(this.doc && (!this.doc.__unsaved) && this.doc.__last_sync_on && (new Date() - this.doc.__last_sync_on) > (this.refresh_if_stale_for * 1000)) { this.reload_doc(); return true; @@ -458,7 +458,7 @@ frappe.ui.form.Form = class FrappeForm { trigger_onload(switched) { this.cscript.is_onload = false; - if (!this.opendocs[this.docname]) { + if(!this.opendocs[this.docname]) { var me = this; this.cscript.is_onload = true; this.initialize_new_doc(); @@ -496,13 +496,13 @@ frappe.ui.form.Form = class FrappeForm { }); // update seen - if (this.meta.track_seen) { + if(this.meta.track_seen) { $('.list-id[data-name="'+ me.docname +'"]').addClass('seen'); } } render_form(switched) { - if (!this.meta.istable) { + if(!this.meta.istable) { this.layout.doc = this.doc; this.layout.attach_doc_and_docfields(); @@ -530,7 +530,7 @@ frappe.ui.form.Form = class FrappeForm { () => this.script_manager.trigger("refresh"), // call onload post render for callbacks to be fired () => { - if (this.cscript.is_onload) { + if(this.cscript.is_onload) { return this.script_manager.trigger("onload_post_render"); } }, @@ -547,7 +547,7 @@ frappe.ui.form.Form = class FrappeForm { this.cscript.is_onload && this.set_first_tab_as_active(); - if (!this.hidden) { + if(!this.hidden) { this.layout.show_empty_form_message(); } @@ -588,7 +588,7 @@ frappe.ui.form.Form = class FrappeForm { } cleanup_refresh() { - if (this.fields_dict['amended_from']) { + if(this.fields_dict['amended_from']) { if (this.doc.amended_from) { unhide_field('amended_from'); if (this.fields_dict['amendment_date']) unhide_field('amendment_date'); @@ -598,15 +598,15 @@ frappe.ui.form.Form = class FrappeForm { } } - if (this.fields_dict['trash_reason']) { - if (this.doc.trash_reason && this.doc.docstatus == 2) { + if(this.fields_dict['trash_reason']) { + if(this.doc.trash_reason && this.doc.docstatus == 2) { unhide_field('trash_reason'); } else { hide_field('trash_reason'); } } - if (this.meta.autoname && this.meta.autoname.substr(0, 6) == 'field:' && !this.doc.__islocal) { + if(this.meta.autoname && this.meta.autoname.substr(0,6)=='field:' && !this.doc.__islocal) { var fn = this.meta.autoname.substr(6); if (this.doc[fn]) { @@ -614,7 +614,7 @@ frappe.ui.form.Form = class FrappeForm { } } - if (this.meta.autoname == "naming_series:" && !this.doc.__islocal) { + if(this.meta.autoname=="naming_series:" && !this.doc.__islocal) { this.toggle_display("naming_series", false); } } @@ -622,12 +622,12 @@ frappe.ui.form.Form = class FrappeForm { refresh_header(switched) { // set title // main title - if (!this.meta.in_dialog || this.in_form) { + if(!this.meta.in_dialog || this.in_form) { frappe.utils.set_title(this.meta.issingle ? this.doctype : this.docname); } // show / hide buttons - if (this.toolbar) { + if(this.toolbar) { if (switched) { this.toolbar.current_status = undefined; } @@ -645,11 +645,11 @@ frappe.ui.form.Form = class FrappeForm { // SAVE save_or_update() { - if (this.save_disabled) return; + if(this.save_disabled) return; - if (this.doc.docstatus === 0) { + if(this.doc.docstatus===0) { this.save(); - } else if (this.doc.docstatus === 1 && this.doc.__unsaved) { + } else if(this.doc.docstatus===1 && this.doc.__unsaved) { this.save("Update"); } } @@ -669,14 +669,14 @@ frappe.ui.form.Form = class FrappeForm { validate_and_save(save_action, callback, btn, on_error, resolve, reject) { var me = this; - if (!save_action) save_action = "Save"; + if(!save_action) save_action = "Save"; this.validate_form_action(save_action, resolve); var after_save = function(r) { // to remove hash from URL to avoid scroll after save history.replaceState(null, null, ' '); - if (!r.exc) { - if (["Save", "Update", "Amend"].indexOf(save_action) !== -1) { + if(!r.exc) { + if (["Save", "Update", "Amend"].indexOf(save_action)!==-1) { frappe.utils.play_sound("click"); } @@ -694,7 +694,7 @@ frappe.ui.form.Form = class FrappeForm { } me.refresh(); } else { - if (on_error) { + if(on_error) { on_error(); reject(); } @@ -708,20 +708,20 @@ frappe.ui.form.Form = class FrappeForm { console.error(e) } btn && $(btn).prop("disabled", false); - if (on_error) { + if(on_error) { on_error(); reject(); } }; - if (save_action != "Update") { + if(save_action != "Update") { // validate frappe.validated = true; frappe.run_serially([ () => this.script_manager.trigger("validate"), () => this.script_manager.trigger("before_save"), () => { - if (!frappe.validated) { + if(!frappe.validated) { fail(); return; } @@ -741,12 +741,12 @@ frappe.ui.form.Form = class FrappeForm { frappe.confirm(__("Permanently Submit {0}?", [this.docname]), function() { frappe.validated = true; me.script_manager.trigger("before_submit").then(function() { - if (!frappe.validated) { + if(!frappe.validated) { return me.handle_save_fail(btn, on_error); } me.save('Submit', function(r) { - if (r.exc) { + if(r.exc) { me.handle_save_fail(btn, on_error); } else { frappe.utils.play_sound("submit"); @@ -935,7 +935,7 @@ frappe.ui.form.Form = class FrappeForm { } if (!this.perm[0][perm_to_check] && !allowed_for_workflow) { - if (resolve) { + if(resolve) { // re-enable buttons resolve(); } @@ -980,7 +980,7 @@ frappe.ui.form.Form = class FrappeForm { // trigger link fields which have default values set if (this.is_new() && this.doc.__run_link_triggers) { $.each(this.fields_dict, function(fieldname, field) { - if (field.df.fieldtype == "Link" && this.doc[fieldname]) { + if (field.df.fieldtype=="Link" && this.doc[fieldname]) { // triggers add fetch, sets value in model and runs triggers field.set_value(this.doc[fieldname], true); } @@ -991,8 +991,8 @@ frappe.ui.form.Form = class FrappeForm { } show_conflict_message() { - if (this.doc.__needs_refresh) { - if (this.doc.__unsaved) { + if(this.doc.__needs_refresh) { + if(this.doc.__unsaved) { this.dashboard.clear_headline(); this.dashboard.set_headline_alert(__("This form has been modified after you have loaded it") + '' @@ -1004,20 +1004,20 @@ frappe.ui.form.Form = class FrappeForm { } show_submit_message() { - if (this.meta.is_submittable + if(this.meta.is_submittable && this.perm[0] && this.perm[0].submit && !this.is_dirty() && !this.is_new() && !frappe.model.has_workflow(this.doctype) // show only if no workflow - && this.doc.docstatus === 0) { + && this.doc.docstatus===0) { this.dashboard.add_comment(__('Submit this document to confirm'), 'blue', true); } } show_web_link() { - if (!this.doc.__islocal && this.doc.__onload && this.doc.__onload.is_website_generator) { + if(!this.doc.__islocal && this.doc.__onload && this.doc.__onload.is_website_generator) { this.web_link && this.web_link.remove(); - if (this.doc.__onload.published) { + if(this.doc.__onload.published) { this.add_web_link("/" + this.doc.route); } } @@ -1034,16 +1034,16 @@ frappe.ui.form.Form = class FrappeForm { var dt = this.parent_doctype ? this.parent_doctype : this.doctype; this.perm = frappe.perm.get_perm(dt, this.doc); - if (!this.perm[0].read) { + if(!this.perm[0].read) { return 0; } return 1; } check_doctype_conflict(docname) { - if (this.doctype == 'DocType' && docname == 'DocType') { + if(this.doctype=='DocType' && docname=='DocType') { frappe.msgprint(__('Allowing DocType, DocType. Be careful!')); - } else if (this.doctype == 'DocType') { + } else if(this.doctype=='DocType') { if (frappe.views.formview[docname] || frappe.pages['List/'+docname]) { window.location.reload(); // frappe.msgprint(__("Cannot open {0} when its instance is open", ['DocType'])) @@ -1062,16 +1062,16 @@ frappe.ui.form.Form = class FrappeForm { // notify this form of renamed records rename_notify(dt, old, name) { // from form - if (this.meta.istable) + if(this.meta.istable) return; - if (this.docname == old) + if(this.docname == old) this.docname = name; else return; // cleanup - if (this && this.opendocs[old] && frappe.meta.docfield_copy[dt]) { + if(this && this.opendocs[old] && frappe.meta.docfield_copy[dt]) { // delete docfield copy frappe.meta.docfield_copy[dt][name] = frappe.meta.docfield_copy[dt][old]; delete frappe.meta.docfield_copy[dt][old]; @@ -1080,7 +1080,7 @@ frappe.ui.form.Form = class FrappeForm { delete this.opendocs[old]; this.opendocs[name] = true; - if (this.meta.in_dialog || !this.in_form) { + if(this.meta.in_dialog || !this.in_form) { return; } @@ -1155,7 +1155,7 @@ frappe.ui.form.Form = class FrappeForm { newdoc.idx = null; newdoc.__run_link_triggers = false; - if (onload) { + if(onload) { onload(newdoc); } frappe.set_route('Form', newdoc.doctype, newdoc.name); @@ -1164,7 +1164,7 @@ frappe.ui.form.Form = class FrappeForm { reload_doc() { this.check_doctype_conflict(this.docname); - if (!this.doc.__islocal) { + if(!this.doc.__islocal) { frappe.model.remove_from_locals(this.doctype, this.docname); return frappe.model.with_doc(this.doctype, this.docname, () => { this.refresh(); @@ -1311,9 +1311,9 @@ frappe.ui.form.Form = class FrappeForm { $.each(fields_list, function(i, fname) { var docfield = frappe.meta.docfield_map[doctype][fname]; - if (docfield) { + if(docfield) { var label = __(docfield.label || "").replace(/\([^\)]*\)/g, ""); // eslint-disable-line - if (parentfield) { + if(parentfield) { grid_field_label_map[doctype + "-" + fname] = label.trim() + " (" + __(currency) + ")"; } else { @@ -1333,8 +1333,8 @@ frappe.ui.form.Form = class FrappeForm { } field_map(fnames, fn) { - if (typeof fnames === 'string') { - if (fnames == '*') { + if(typeof fnames==='string') { + if(fnames == '*') { fnames = Object.keys(this.fields_dict); } else { fnames = [fnames]; @@ -1343,7 +1343,7 @@ frappe.ui.form.Form = class FrappeForm { for (var i=0, l=fnames.length; i { - if (!unique_keys.includes(key)) { + if(!unique_keys.includes(key)) { d[key] = values[key]; } }); @@ -1448,9 +1448,9 @@ frappe.ui.form.Form = class FrappeForm { var me = this; var _set = function(f, v) { var fieldobj = me.fields_dict[f]; - if (fieldobj) { - if (!if_missing || !frappe.model.has_value(me.doctype, me.doc.name, f)) { - if (frappe.model.table_fields.includes(fieldobj.df.fieldtype) && $.isArray(v)) { + if(fieldobj) { + if(!if_missing || !frappe.model.has_value(me.doctype, me.doc.name, f)) { + if(frappe.model.table_fields.includes(fieldobj.df.fieldtype) && $.isArray(v)) { frappe.model.clear_table(me.doc, fieldobj.df.fieldname); @@ -1473,13 +1473,13 @@ frappe.ui.form.Form = class FrappeForm { } }; - if (typeof field == "string") { + if(typeof field=="string") { return _set(field, value); - } else if ($.isPlainObject(field)) { + } else if($.isPlainObject(field)) { let tasks = []; for (let f in field) { let v = field[f]; - if (me.get_field(f)) { + if(me.get_field(f)) { tasks.push(() => _set(f, v)); } } @@ -1489,7 +1489,7 @@ frappe.ui.form.Form = class FrappeForm { call(opts, args, callback) { var me = this; - if (typeof opts === 'string') { + if(typeof opts==='string') { // called as frm.call('do_this', {with_arg: 'arg'}); opts = { method: opts, @@ -1498,19 +1498,19 @@ frappe.ui.form.Form = class FrappeForm { callback: callback }; } - if (!opts.doc) { - if (opts.method.indexOf(".") === -1) + if(!opts.doc) { + if(opts.method.indexOf(".")===-1) opts.method = frappe.model.get_server_module_name(me.doctype) + "." + opts.method; opts.original_callback = opts.callback; opts.callback = function(r) { - if ($.isPlainObject(r.message)) { - if (opts.child) { + if($.isPlainObject(r.message)) { + if(opts.child) { // update child doc opts.child = locals[opts.child.doctype][opts.child.name]; var std_field_list = ["doctype"].concat(frappe.model.std_fields_list); for (var key in r.message) { - if (std_field_list.indexOf(key) === -1) { + if (std_field_list.indexOf(key)===-1) { opts.child[key] = r.message[key]; } } @@ -1526,7 +1526,7 @@ frappe.ui.form.Form = class FrappeForm { } else { opts.original_callback = opts.callback; opts.callback = function(r) { - if (!r.exc) me.refresh_fields(); + if(!r.exc) me.refresh_fields(); opts.original_callback && opts.original_callback(r); }; @@ -1567,7 +1567,7 @@ frappe.ui.form.Form = class FrappeForm { } get_title() { - if (this.meta.title_field) { + if(this.meta.title_field) { return this.doc[this.meta.title_field]; } else { return this.doc.name; @@ -1581,11 +1581,11 @@ frappe.ui.form.Form = class FrappeForm { // handle TableMultiselect child fields let _selected = []; - if (me.fields_dict[df.fieldname].grid) { + if(me.fields_dict[df.fieldname].grid) { _selected = me.fields_dict[df.fieldname].grid.get_selected(); } - if (_selected.length) { + if(_selected.length) { selected[df.fieldname] = _selected; } }); @@ -1595,11 +1595,11 @@ frappe.ui.form.Form = class FrappeForm { set_indicator_formatter(fieldname, get_color, get_text) { // get doctype from parent var doctype; - if (frappe.meta.docfield_map[this.doctype][fieldname]) { + if(frappe.meta.docfield_map[this.doctype][fieldname]) { doctype = this.doctype; } else { frappe.meta.get_table_fields(this.doctype).every(function(df) { - if (frappe.meta.docfield_map[df.options][fieldname]) { + if(frappe.meta.docfield_map[df.options][fieldname]) { doctype = df.options; return false; } else { @@ -1610,11 +1610,11 @@ frappe.ui.form.Form = class FrappeForm { frappe.meta.docfield_map[doctype][fieldname].formatter = function(value, df, options, doc) { - if (value) { + if(value) { var label; - if (get_text) { + if(get_text) { label = get_text(doc); - } else if (frappe.form.link_formatters[df.options]) { + } else if(frappe.form.link_formatters[df.options]) { label = frappe.form.link_formatters[df.options](value, doc); } else { label = value; @@ -1633,21 +1633,21 @@ frappe.ui.form.Form = class FrappeForm { // return true or false if the user can make a particlar doctype // will check permission, `can_make_methods` if exists, or will decided on // basis of whether the document is submittable - if (!frappe.model.can_create(doctype)) { + if(!frappe.model.can_create(doctype)) { return false; } - if (this.custom_make_buttons && this.custom_make_buttons[doctype]) { + if(this.custom_make_buttons && this.custom_make_buttons[doctype]) { // custom buttons are translated and so are the keys const key = __(this.custom_make_buttons[doctype]); // if the button is present, then show make return !!this.custom_buttons[key]; } - if (this.can_make_methods && this.can_make_methods[doctype]) { + if(this.can_make_methods && this.can_make_methods[doctype]) { return this.can_make_methods[doctype](this); } else { - if (this.meta.is_submittable && !this.doc.docstatus == 1) { + if(this.meta.is_submittable && !this.doc.docstatus==1) { return false; } else { return true; @@ -1660,9 +1660,9 @@ frappe.ui.form.Form = class FrappeForm { // will handover to `make_methods` if defined // or will create and match link fields let me = this; - if (this.make_methods && this.make_methods[doctype]) { + if(this.make_methods && this.make_methods[doctype]) { return this.make_methods[doctype](this); - } else if (this.custom_make_buttons && this.custom_make_buttons[doctype]) { + } else if(this.custom_make_buttons && this.custom_make_buttons[doctype]) { this.custom_buttons[__(this.custom_make_buttons[doctype])].trigger('click'); } else { frappe.model.with_doctype(doctype, function() { @@ -1693,10 +1693,10 @@ frappe.ui.form.Form = class FrappeForm { update_in_all_rows(table_fieldname, fieldname, value) { // update the child value in all tables where it is missing - if (!value) return; + if(!value) return; var cl = this.doc[table_fieldname] || []; - for (var i = 0; i < cl.length; i++) { - if (!cl[i][fieldname]) cl[i][fieldname] = value; + for(var i = 0; i < cl.length; i++){ + if(!cl[i][fieldname]) cl[i][fieldname] = value; } refresh_field("items"); } From 20056a447febf8c507060372ab91078ead54e946 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 16 Feb 2022 17:21:59 +0530 Subject: [PATCH 28/71] ci: Track server-side coverage as well while doing UI tests --- .github/helper/install.sh | 4 +++- .github/workflows/ui-tests.yml | 19 ++++++++++++++++++- frappe/commands/utils.py | 11 ++++++++--- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 19a7c68e19..26b3f28ba2 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -50,7 +50,9 @@ if [ "$TYPE" == "server" ]; then sed -i 's/^socketio:/# socketio:/g' Procfile; f if [ "$TYPE" == "server" ]; then sed -i 's/^redis_socketio:/# redis_socketio:/g' Procfile; fi if [ "$TYPE" == "ui" ]; then bench setup requirements --node; fi -if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi +bench setup requirements --dev + +if [ "$TYPE" == "ui" ]; then sed -i 's/^web: bench serve/web: bench serve --with-coverage/g' Procfile; fi # install node-sass which is required for website theme test cd ./apps/frappe || exit diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 3eefd1ce82..fb09768c81 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -118,7 +118,9 @@ jobs: - name: Install if: ${{ steps.check-build.outputs.build == 'strawberry' }} - run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh + run: | + bash ${GITHUB_WORKSPACE}/.github/helper/install.sh + cat ~/frappe-bench/Procfile env: DB: mariadb TYPE: ui @@ -141,6 +143,11 @@ jobs: env: CYPRESS_RECORD_KEY: 4a48f41c-11b3-425b-aa88-c58048fa69eb + - name: Stop server + run: | + ps -ef | grep "frappe serve" | awk '{print $2}' | xargs kill -s SIGINT 2> /dev/null || true + sleep 5 + - name: Check If Coverage Report Exists id: check_coverage uses: andstor/file-existence-action@v1 @@ -156,3 +163,13 @@ jobs: directory: /home/runner/frappe-bench/apps/frappe/.cypress-coverage/ verbose: true flags: ui-tests + + - name: Upload Server Coverage Data + if: ${{ steps.check-build.outputs.build == 'strawberry' }} + uses: codecov/codecov-action@v2 + with: + name: MariaDB + fail_ci_if_error: true + files: /home/runner/frappe-bench/sites/coverage.xml + verbose: true + flags: server diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index e3379a43aa..7246df8aa7 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -742,8 +742,9 @@ def run_ui_tests(context, app, headless=False, parallel=True, with_coverage=Fals @click.option('--profile', is_flag=True, default=False) @click.option('--noreload', "no_reload", is_flag=True, default=False) @click.option('--nothreading', "no_threading", is_flag=True, default=False) +@click.option('--with-coverage', is_flag=True, default=False) @pass_context -def serve(context, port=None, profile=False, no_reload=False, no_threading=False, sites_path='.', site=None): +def serve(context, port=None, profile=False, no_reload=False, no_threading=False, sites_path='.', site=None, with_coverage=False): "Start development web server" import frappe.app @@ -751,8 +752,12 @@ def serve(context, port=None, profile=False, no_reload=False, no_threading=False site = None else: site = context.sites[0] - - frappe.app.serve(port=port, profile=profile, no_reload=no_reload, no_threading=no_threading, site=site, sites_path='.') + with CodeCoverage(with_coverage, 'frappe'): + if with_coverage: + # unable to track coverage with threading enabled + no_threading = True + no_reload = True + frappe.app.serve(port=port, profile=profile, no_reload=no_reload, no_threading=no_threading, site=site, sites_path='.') @click.command('request') From 4e24efda8f7aae035fcdc772b89c683c8450dfb6 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 16 Feb 2022 17:44:14 +0530 Subject: [PATCH 29/71] ci: Exclude coverage.py and build.py from the report --- .github/workflows/ui-tests.yml | 4 +--- frappe/coverage.py | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index fb09768c81..91369dcaaa 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -118,9 +118,7 @@ jobs: - name: Install if: ${{ steps.check-build.outputs.build == 'strawberry' }} - run: | - bash ${GITHUB_WORKSPACE}/.github/helper/install.sh - cat ~/frappe-bench/Procfile + run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh env: DB: mariadb TYPE: ui diff --git a/frappe/coverage.py b/frappe/coverage.py index 1969cae141..e76bdaae90 100644 --- a/frappe/coverage.py +++ b/frappe/coverage.py @@ -29,6 +29,8 @@ FRAPPE_EXCLUSIONS = [ "*/commands/*", "*/frappe/change_log/*", "*/frappe/exceptions*", + "*/frappe/coverage.py", + "*/frappe/build.py", "*frappe/setup.py", "*/doctype/*/*_dashboard.py", "*/patches/*", From 4fb22d0f3419995076183abc860cd95bcfecbed7 Mon Sep 17 00:00:00 2001 From: Benedict Allerberger Date: Wed, 16 Feb 2022 22:23:27 +0100 Subject: [PATCH 30/71] fix: broken validation for custom LDAP server --- frappe/integrations/doctype/ldap_settings/ldap_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index 7c9c64ba3c..3d29feebac 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -45,8 +45,8 @@ class LDAPSettings(Document): title=_("Misconfigured")) if self.ldap_directory_server.lower() == 'custom': - if not self.ldap_group_member_attribute or not self.ldap_group_mappings_section: - frappe.throw(_("Custom LDAP Directoy Selected, please ensure 'LDAP Group Member attribute' and 'LDAP Group Mappings' are entered"), + if not self.ldap_group_member_attribute or not self.ldap_group_objectclass: + frappe.throw(_("Custom LDAP Directoy Selected, please ensure 'LDAP Group Member attribute' and 'Group Object Class' are entered"), title=_("Misconfigured")) else: From e0dcabf22499c6c900b7d72061ff7662a213d609 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 17 Feb 2022 09:52:49 +0530 Subject: [PATCH 31/71] ci: Build not required for UI tests Since we build again after instrumentation --- .github/helper/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index 26b3f28ba2..246bdbe096 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -62,4 +62,4 @@ cd ../.. bench start & bench --site test_site reinstall --yes if [ "$TYPE" == "server" ]; then bench --site test_site_producer reinstall --yes; fi -CI=Yes bench build --app frappe +if [ "$TYPE" == "server" ]; then CI=Yes bench build --app frappe; fi From de22bd6297bd0224e291b8856e532432f199fcaa Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 17 Feb 2022 10:35:15 +0530 Subject: [PATCH 32/71] fix: improved validation --- frappe/utils/backups.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index 2ef53a3343..69f4ee2b01 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -653,7 +653,8 @@ def get_backup_path(): @frappe.whitelist() def get_backup_encryption_key(): - return frappe.local.conf.encryption_key + frappe.only("System Manager") + return frappe.conf.encryption_key class Backup: def __init__(self, file_path): From 0f95c2716de3d10cf27ba01556b3f8d7abab0e0d Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Thu, 17 Feb 2022 10:50:31 +0530 Subject: [PATCH 33/71] fix: incorrect function name in call --- frappe/utils/backups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index 69f4ee2b01..d23804bef4 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -653,7 +653,7 @@ def get_backup_path(): @frappe.whitelist() def get_backup_encryption_key(): - frappe.only("System Manager") + frappe.only_for("System Manager") return frappe.conf.encryption_key class Backup: From 383eb668bf90c38594d6ebbde41962e4e12d06c6 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 17 Feb 2022 11:27:34 +0530 Subject: [PATCH 34/71] fix: Update docfields of a doctype while doing update_docfield_property - This is necessary to get updated docfields while adding new row --- frappe/public/js/frappe/form/grid.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 11fda3c8b0..98ab573b07 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -972,6 +972,7 @@ export default class Grid { // update the parent too (for new rows) this.docfields.find(d => d.fieldname === fieldname)[property] = value; + frappe.meta.docfield_map[this.doctype][fieldname][property] = value; this.debounced_refresh(); } From bb00c34e72bd4a9ae7f06289187904acf9da02f3 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 17 Feb 2022 11:30:28 +0530 Subject: [PATCH 35/71] fix: Update grid-row docfield instead of updating doctype meta --- frappe/public/js/frappe/form/grid.js | 3 ++- frappe/public/js/frappe/form/grid_row.js | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 98ab573b07..5dc59120fd 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -368,6 +368,8 @@ export default class Grid { if (this.grid_rows[ri] && !append_row) { var grid_row = this.grid_rows[ri]; grid_row.doc = d; + // reset docfield list for this row + grid_row.docfields = this.docfields; grid_row.refresh(); } else { var grid_row = new GridRow({ @@ -972,7 +974,6 @@ export default class Grid { // update the parent too (for new rows) this.docfields.find(d => d.fieldname === fieldname)[property] = value; - frappe.meta.docfield_map[this.doctype][fieldname][property] = value; this.debounced_refresh(); } diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index a40f428969..441a879369 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -5,16 +5,13 @@ export default class GridRow { this.on_grid_fields_dict = {}; this.on_grid_fields = []; $.extend(this, opts); - if (this.doc && this.parent_df.options) { - frappe.meta.make_docfield_copy_for(this.parent_df.options, this.doc.name, this.docfields); - const docfields = frappe.meta.get_docfields(this.parent_df.options, this.doc.name); - this.docfields = docfields.length ? docfields : opts.docfields; - } + this.set_docfields(); this.columns = {}; this.columns_list = []; this.row_check_html = ''; this.make(); } + make() { var me = this; @@ -41,6 +38,15 @@ export default class GridRow { this.set_data(); } } + + set_docfields() { + if (this.doc && this.parent_df.options) { + frappe.meta.make_docfield_copy_for(this.parent_df.options, this.doc.name, this.docfields); + const docfields = frappe.meta.get_docfields(this.parent_df.options, this.doc.name); + this.docfields = docfields; + } + } + set_data() { this.wrapper.data({ "doc": this.doc @@ -152,6 +158,8 @@ export default class GridRow { this.doc = locals[this.doc.doctype][this.doc.name]; } + // update docfield list + this.set_docfields(); if(this.grid.template && !this.grid.meta.editable_grid) { this.render_template(); } else { From a8c9b92a013da438f25c873e93a1085148875a83 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 17 Feb 2022 12:16:35 +0530 Subject: [PATCH 36/71] fix: No need to reset docfields, grid row already has df copy --- frappe/public/js/frappe/form/grid.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 5dc59120fd..11fda3c8b0 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -368,8 +368,6 @@ export default class Grid { if (this.grid_rows[ri] && !append_row) { var grid_row = this.grid_rows[ri]; grid_row.doc = d; - // reset docfield list for this row - grid_row.docfields = this.docfields; grid_row.refresh(); } else { var grid_row = new GridRow({ From 0a1cd9e623061dc563224dfbc941f003f82a83b4 Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Thu, 17 Feb 2022 15:43:07 +0530 Subject: [PATCH 37/71] fix: Removed verbose flag for gzip --- frappe/installer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/installer.py b/frappe/installer.py index d892ff4ddc..0948620ab0 100644 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -529,10 +529,9 @@ def extract_sql_gzip(sql_gz_path): import subprocess try: - # dvf - decompress, verbose, force original_file = sql_gz_path decompressed_file = original_file.rstrip(".gz") - cmd = 'gzip -dvf < {0} > {1}'.format(original_file, decompressed_file) + cmd = 'gzip --decompress --force < {0} > {1}'.format(original_file, decompressed_file) subprocess.check_call(cmd, shell=True) except Exception: raise From 628c9bc2881abadb83fc1f7211cbffecbae48cec Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 17 Feb 2022 16:57:27 +0530 Subject: [PATCH 38/71] ci: Remove build.py from exclusion list Co-authored-by: gavin --- frappe/coverage.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/coverage.py b/frappe/coverage.py index e76bdaae90..5f89800deb 100644 --- a/frappe/coverage.py +++ b/frappe/coverage.py @@ -30,7 +30,6 @@ FRAPPE_EXCLUSIONS = [ "*/frappe/change_log/*", "*/frappe/exceptions*", "*/frappe/coverage.py", - "*/frappe/build.py", "*frappe/setup.py", "*/doctype/*/*_dashboard.py", "*/patches/*", From 9262d2774e00a4a4c12080f23983593790a01368 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 17 Feb 2022 18:50:33 +0530 Subject: [PATCH 39/71] fix(grid): Update docfields for new record --- frappe/public/js/frappe/form/grid_row.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 441a879369..0ee5a180e0 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -11,7 +11,6 @@ export default class GridRow { this.row_check_html = ''; this.make(); } - make() { var me = this; @@ -39,11 +38,18 @@ export default class GridRow { } } - set_docfields() { + set_docfields(update=false) { if (this.doc && this.parent_df.options) { frappe.meta.make_docfield_copy_for(this.parent_df.options, this.doc.name, this.docfields); const docfields = frappe.meta.get_docfields(this.parent_df.options, this.doc.name); - this.docfields = docfields; + if (update) { + // to maintain references + this.docfields.forEach(df => { + Object.assign(df, docfields.find(d => d.fieldname === df.fieldname)); + }); + } else { + this.docfields = docfields; + } } } @@ -154,12 +160,15 @@ export default class GridRow { }, __('Move To'), 'Update'); } refresh() { + // update docfields for new record + if (this.frm && this.doc && this.doc.__islocal) { + this.set_docfields(true); + } + if(this.frm && this.doc) { this.doc = locals[this.doc.doctype][this.doc.name]; } - // update docfield list - this.set_docfields(); if(this.grid.template && !this.grid.meta.editable_grid) { this.render_template(); } else { From 74748c60b2cb9e84a6c68aa3a873dbc31ee52635 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 18 Feb 2022 12:45:21 +0530 Subject: [PATCH 40/71] test: depends on for tabs --- cypress/integration/depends_on.js | 21 +++++++++++++++++++++ frappe/public/js/frappe/form/tab.js | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/cypress/integration/depends_on.js b/cypress/integration/depends_on.js index 9aa6b5d89d..c0dbbf63f6 100644 --- a/cypress/integration/depends_on.js +++ b/cypress/integration/depends_on.js @@ -55,10 +55,31 @@ context('Depends On', () => { 'read_only_depends_on': "eval:doc.test_field=='Some Other Value'", 'options': "Child Test Depends On" }, + { + "label": "Dependent Tab", + "fieldname": "dependent_tab", + "fieldtype": "Tab Break", + "depends_on": "eval:doc.test_field=='Show Tab'" + }, + { + "fieldname": "tab_section", + "fieldtype": "Section Break", + }, + { + "label": "Field in Tab", + "fieldname": "field_in_tab", + "fieldtype": "Data", + } ] }); }); }); + it('should show the tab on other setting field value', () => { + cy.new_form('Test Depends On'); + cy.fill_field('test_field', 'Show Tab'); + cy.get('body').click(); + cy.findByRole("tab", {name: "Dependent Tab"}).should('be.visible'); + }) it('should set the field as mandatory depending on other fields value', () => { cy.new_form('Test Depends On'); cy.fill_field('test_field', 'Some Value'); diff --git a/frappe/public/js/frappe/form/tab.js b/frappe/public/js/frappe/form/tab.js index c8ca016398..7df4d3a0cc 100644 --- a/frappe/public/js/frappe/form/tab.js +++ b/frappe/public/js/frappe/form/tab.js @@ -40,7 +40,7 @@ export default class Tab { hide = true; } - hide && this.toggle(false); + this.toggle(!hide) } toggle(show) { From b8539028909f7786f6be21ab6c8a1dc861496afa Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 18 Feb 2022 13:03:31 +0530 Subject: [PATCH 41/71] chore: add missing semicolon --- cypress/integration/depends_on.js | 2 +- frappe/public/js/frappe/form/tab.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress/integration/depends_on.js b/cypress/integration/depends_on.js index c0dbbf63f6..12f54f2b6e 100644 --- a/cypress/integration/depends_on.js +++ b/cypress/integration/depends_on.js @@ -79,7 +79,7 @@ context('Depends On', () => { cy.fill_field('test_field', 'Show Tab'); cy.get('body').click(); cy.findByRole("tab", {name: "Dependent Tab"}).should('be.visible'); - }) + }); it('should set the field as mandatory depending on other fields value', () => { cy.new_form('Test Depends On'); cy.fill_field('test_field', 'Some Value'); diff --git a/frappe/public/js/frappe/form/tab.js b/frappe/public/js/frappe/form/tab.js index 7df4d3a0cc..0e740ce49c 100644 --- a/frappe/public/js/frappe/form/tab.js +++ b/frappe/public/js/frappe/form/tab.js @@ -40,7 +40,7 @@ export default class Tab { hide = true; } - this.toggle(!hide) + this.toggle(!hide); } toggle(show) { From 2d5c9651df262ccbf2c21956970ac18c1902aab8 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Fri, 18 Feb 2022 18:10:10 +0530 Subject: [PATCH 42/71] fix: share clickable URLs from List View --- frappe/public/js/frappe/list/list_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 64960e0b09..5cfc7c75d4 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -1483,7 +1483,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { return [ filter[1], "=", - JSON.stringify([filter[2], filter[3]]), + encodeURIComponent(JSON.stringify([filter[2], filter[3]])), ].join(""); }) .join("&"); From d5ce2b4a8c10af3bad7c74ec2043fb882f6ecd4f Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Sat, 19 Feb 2022 21:22:35 +0530 Subject: [PATCH 43/71] fix: Totalling for tree view reports (#15950) --- frappe/core/doctype/report/test_report.py | 54 ++++++++++++++++++- frappe/desk/query_report.py | 24 ++++++--- .../js/frappe/views/reports/query_report.js | 5 +- 3 files changed, 73 insertions(+), 10 deletions(-) diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index 5a22304f32..bf63afa5c5 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -3,7 +3,7 @@ import frappe, json, os import unittest -from frappe.desk.query_report import run, save_report +from frappe.desk.query_report import run, save_report, add_total_row from frappe.desk.reportview import delete_report, save_report as _save_report from frappe.custom.doctype.customize_form.customize_form import reset_customization from frappe.core.doctype.user_permission.test_user_permission import create_user @@ -282,3 +282,55 @@ result = [ # Set user back to administrator frappe.set_user('Administrator') + + def test_add_total_row_for_tree_reports(self): + report_settings = { + 'tree': True, + 'parent_field': 'parent_value' + } + + columns = [ + { + "fieldname": "parent_column", + "label": "Parent Column", + "fieldtype": "Data", + "width": 10 + }, + { + "fieldname": "column_1", + "label": "Column 1", + "fieldtype": "Float", + "width": 10 + }, + { + "fieldname": "column_2", + "label": "Column 2", + "fieldtype": "Float", + "width": 10 + } + ] + + result = [ + { + "parent_column": "Parent 1", + "column_1": 200, + "column_2": 150.50 + }, + { + "parent_column": "Child 1", + "column_1": 100, + "column_2": 75.25, + "parent_value": "Parent 1" + }, + { + "parent_column": "Child 2", + "column_1": 100, + "column_2": 75.25, + "parent_value": "Parent 1" + } + ] + + result = add_total_row(result, columns, meta=None, report_settings=report_settings) + self.assertEqual(result[-1][0], "Total") + self.assertEqual(result[-1][1], 200) + self.assertEqual(result[-1][2], 150.50) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 97bceeb725..9ed956e986 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -73,7 +73,7 @@ def get_report_result(report, filters): return res @frappe.read_only() -def generate_report_result(report, filters=None, user=None, custom_columns=None): +def generate_report_result(report, filters=None, user=None, custom_columns=None, report_settings=None): user = user or frappe.session.user filters = filters or [] @@ -108,7 +108,7 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None) result = get_filtered_data(report.ref_doctype, columns, result, user) if cint(report.add_total_row) and result and not skip_total_row: - result = add_total_row(result, columns) + result = add_total_row(result, columns, report_settings=report_settings) return { "result": result, @@ -210,7 +210,7 @@ def get_script(report_name): @frappe.whitelist() @frappe.read_only() -def run(report_name, filters=None, user=None, ignore_prepared_report=False, custom_columns=None): +def run(report_name, filters=None, user=None, ignore_prepared_report=False, custom_columns=None, report_settings=None): report = get_report_doc(report_name) if not user: user = frappe.session.user @@ -238,7 +238,7 @@ def run(report_name, filters=None, user=None, ignore_prepared_report=False, cust dn = "" result = get_prepared_report_result(report, filters, dn, user) else: - result = generate_report_result(report, filters, user, custom_columns) + result = generate_report_result(report, filters, user, custom_columns, report_settings) result["add_total_row"] = report.add_total_row and not result.get( "skip_total_row", False @@ -435,9 +435,19 @@ def build_xlsx_data(columns, data, visible_idx, include_indentation, ignore_visi return result, column_widths -def add_total_row(result, columns, meta=None): +def add_total_row(result, columns, meta=None, report_settings=None): total_row = [""] * len(columns) has_percent = [] + is_tree = False + parent_field = '' + + if report_settings: + if isinstance(report_settings, (str,)): + report_settings = json.loads(report_settings) + + is_tree = report_settings.get('tree') + parent_field = report_settings.get('parent_field') + for i, col in enumerate(columns): fieldtype, options, fieldname = None, None, None if isinstance(col, str): @@ -464,12 +474,12 @@ def add_total_row(result, columns, meta=None): for row in result: if i >= len(row): continue - cell = row.get(fieldname) if isinstance(row, dict) else row[i] if fieldtype in ["Currency", "Int", "Float", "Percent", "Duration"] and flt( cell ): - total_row[i] = flt(total_row[i]) + flt(cell) + if not (is_tree and row.get(parent_field)): + total_row[i] = flt(total_row[i]) + flt(cell) if fieldtype == "Percent" and i not in has_percent: has_percent.append(i) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 7ba0a0228f..c226a3a458 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -578,6 +578,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { args: { report_name: this.report_name, filters: filters, + report_settings: this.report_settings }, callback: resolve, always: () => this.page.btn_secondary.prop('disabled', false) @@ -834,7 +835,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { let data = this.data; let columns = this.columns.filter((col) => !col.hidden); - if (this.raw_data.add_total_row) { + if (this.raw_data.add_total_row && !this.report_settings.tree) { data = data.slice(); data.splice(-1, 1); } @@ -854,7 +855,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { treeView: this.tree_report, layout: 'fixed', cellHeight: 33, - showTotalRow: this.raw_data.add_total_row, + showTotalRow: this.raw_data.add_total_row && !this.report_settings.tree, direction: frappe.utils.is_rtl() ? 'rtl' : 'ltr', hooks: { columnTotal: frappe.utils.report_column_total From 5de89df5b77d6b02f7662ac21006aa3301192f08 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 21 Feb 2022 09:32:44 +0530 Subject: [PATCH 44/71] ci: Only upload server-side coverage report if .py files are changed --- .github/helper/roulette.py | 13 +++++++++---- .github/workflows/ui-tests.yml | 3 ++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/helper/roulette.py b/.github/helper/roulette.py index 9831df7f30..f0caa74e96 100644 --- a/.github/helper/roulette.py +++ b/.github/helper/roulette.py @@ -52,7 +52,8 @@ if __name__ == "__main__": ci_files_changed = any(f for f in files_list if is_ci(f)) only_docs_changed = len(list(filter(is_docs, files_list))) == len(files_list) only_frontend_code_changed = len(list(filter(is_frontend_code, files_list))) == len(files_list) - only_py_changed = len(list(filter(is_py, files_list))) == len(files_list) + updated_py_file_count = len(list(filter(is_py, files_list))) + only_py_changed = updated_py_file_count == len(files_list) if ci_files_changed: print("CI related files were updated, running all build processes.") @@ -65,8 +66,12 @@ if __name__ == "__main__": print("Only Frontend code was updated; Stopping Python build process.") sys.exit(0) - elif only_py_changed and build_type == "ui": - print("Only Python code was updated, stopping Cypress build process.") - sys.exit(0) + elif build_type == "ui": + if only_py_changed: + print("Only Python code was updated, stopping Cypress build process.") + sys.exit(0) + elif updated_py_file_count > 0: + # both frontend and backend code were updated + os.system('echo "::set-output name=build-server::strawberry"') os.system('echo "::set-output name=build::strawberry"') diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 91369dcaaa..fc8093444e 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -142,6 +142,7 @@ jobs: CYPRESS_RECORD_KEY: 4a48f41c-11b3-425b-aa88-c58048fa69eb - name: Stop server + if: ${{ steps.check-build.outputs.build-server == 'strawberry' }} run: | ps -ef | grep "frappe serve" | awk '{print $2}' | xargs kill -s SIGINT 2> /dev/null || true sleep 5 @@ -163,7 +164,7 @@ jobs: flags: ui-tests - name: Upload Server Coverage Data - if: ${{ steps.check-build.outputs.build == 'strawberry' }} + if: ${{ steps.check-build.outputs.build-server == 'strawberry' }} uses: codecov/codecov-action@v2 with: name: MariaDB From 21fcd00416fac36cbbbf0f1187f62ba4451ed369 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 21 Feb 2022 11:07:37 +0530 Subject: [PATCH 45/71] ci: While running all builds set build-server as well --- .github/helper/roulette.py | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/helper/roulette.py b/.github/helper/roulette.py index f0caa74e96..90f4608a22 100644 --- a/.github/helper/roulette.py +++ b/.github/helper/roulette.py @@ -41,6 +41,7 @@ if __name__ == "__main__": # this is a push build, run all builds if not pr_number: os.system('echo "::set-output name=build::strawberry"') + os.system('echo "::set-output name=build-server::strawberry"') sys.exit(0) files_list = files_list or get_files_list(pr_number=pr_number, repo=repo) From 25af0c82173d4a7fb28fa7a5c4d9c0e5c723edb8 Mon Sep 17 00:00:00 2001 From: lapardnemihk1099 Date: Mon, 21 Feb 2022 13:36:39 +0530 Subject: [PATCH 46/71] chore: unnecessary JS warning if options are not defined in date field --- frappe/public/js/frappe/form/controls/date.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/controls/date.js b/frappe/public/js/frappe/form/controls/date.js index 7ad1887d62..fa9dc1c99e 100644 --- a/frappe/public/js/frappe/form/controls/date.js +++ b/frappe/public/js/frappe/form/controls/date.js @@ -158,8 +158,10 @@ frappe.ui.form.ControlDate = class ControlDate extends frappe.ui.form.ControlDat return value; } get_df_options() { + if(!this.df.options) return {}; + let options = {}; - let df_options = this.df.options || ''; + let df_options = this.df.options; if (typeof df_options === 'string') { try { options = JSON.parse(df_options); From e562368069a5e604d22b2bdcac34368c92fee9fd Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 21 Feb 2022 13:43:53 +0530 Subject: [PATCH 47/71] fix: Fix if table_field exists in meta of link.parent_doctype --- frappe/core/doctype/doctype/doctype.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 6d0409521e..7d75ef7ba9 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -784,10 +784,9 @@ def validate_links_table_fieldnames(meta): if not meta.links or frappe.flags.in_patch or frappe.flags.in_fixtures: return - fieldnames = tuple(field.fieldname for field in meta.fields) for index, link in enumerate(meta.links, 1): link_meta = frappe.get_meta(link.link_doctype) - if not link_meta.get_field(link.link_fieldname): + if not frappe.get_meta(link.link_doctype).has_field(link.link_fieldname): message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(index, frappe.bold(link.link_fieldname), frappe.bold(link.link_doctype)) frappe.throw(message, InvalidFieldNameError, _("Invalid Fieldname")) @@ -802,7 +801,7 @@ def validate_links_table_fieldnames(meta): message = _("Document Links Row #{0}: Table Fieldname is mandatory for internal links").format(index) frappe.throw(message, frappe.ValidationError, _("Table Fieldname Missing")) - if link.table_fieldname not in fieldnames: + if not frappe.get_meta(link.parent_doctype).has_field(link.table_fieldname): message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(index, frappe.bold(link.table_fieldname), frappe.bold(meta.name)) frappe.throw(message, frappe.ValidationError, _("Invalid Table Fieldname")) From 88791c22648ab25fb95543441646b81a2c7ca60a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 21 Feb 2022 14:25:34 +0530 Subject: [PATCH 48/71] test: Set correct parent doctype for document link --- frappe/custom/doctype/customize_form/test_customize_form.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/custom/doctype/customize_form/test_customize_form.py b/frappe/custom/doctype/customize_form/test_customize_form.py index 0fe39e0008..d131f06127 100644 --- a/frappe/custom/doctype/customize_form/test_customize_form.py +++ b/frappe/custom/doctype/customize_form/test_customize_form.py @@ -257,7 +257,7 @@ class TestCustomizeForm(unittest.TestCase): frappe.clear_cache() d = self.get_customize_form("User Group") - d.append('links', dict(link_doctype='User Group Member', parent_doctype='User', + d.append('links', dict(link_doctype='User Group Member', parent_doctype='User Group', link_fieldname='user', table_fieldname='user_group_members', group='Tests', custom=1)) d.run_method("save_customization") @@ -267,7 +267,7 @@ class TestCustomizeForm(unittest.TestCase): # check links exist self.assertTrue([d.name for d in user_group.links if d.link_doctype == 'User Group Member']) - self.assertTrue([d.name for d in user_group.links if d.parent_doctype == 'User']) + self.assertTrue([d.name for d in user_group.links if d.parent_doctype == 'User Group']) # remove the link d = self.get_customize_form("User Group") From 745297a49d516e5e3c4bb3e1b0c4235e7d31165d Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 21 Feb 2022 19:54:34 +0100 Subject: [PATCH 49/71] refactor: a not in b Search: if not ([\w\d]*?) in ([\w\d]*?) Replace: if $1 not in $2 --- frappe/database/database.py | 2 +- frappe/desk/reportview.py | 5 +++-- frappe/desk/treeview.py | 2 +- frappe/installer.py | 2 +- frappe/model/db_query.py | 4 ++-- frappe/model/rename_doc.py | 2 +- frappe/model/sync.py | 2 +- frappe/realtime.py | 2 +- frappe/test_runner.py | 4 ++-- frappe/translate.py | 2 +- frappe/utils/redis_wrapper.py | 4 ++-- frappe/utils/user.py | 2 +- frappe/website/utils.py | 2 +- 13 files changed, 18 insertions(+), 17 deletions(-) mode change 100755 => 100644 frappe/utils/user.py diff --git a/frappe/database/database.py b/frappe/database/database.py index c833bdeed3..dc9f20d8c2 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -584,7 +584,7 @@ class Database(object): company = frappe.db.get_single_value('Global Defaults', 'default_company') """ - if not doctype in self.value_cache: + if doctype not in self.value_cache: self.value_cache[doctype] = {} if cache and fieldname in self.value_cache[doctype]: diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index b0e1f901aa..1ec8ede62e 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -533,7 +533,8 @@ def get_stats(stats, doctype, filters=None): columns = [] for tag in tags: - if not tag in columns: continue + if tag not in columns: + continue try: tag_count = frappe.get_list(doctype, fields=[tag, "count(*)"], @@ -612,7 +613,7 @@ def scrub_user_tags(tagcount): alltags = t.split(',') for tag in alltags: if tag: - if not tag in rdict: + if tag not in rdict: rdict[tag] = 0 rdict[tag] += tagdict[t] diff --git a/frappe/desk/treeview.py b/frappe/desk/treeview.py index 7e3efb5d48..5e8fb18fe4 100644 --- a/frappe/desk/treeview.py +++ b/frappe/desk/treeview.py @@ -15,7 +15,7 @@ def get_all_nodes(doctype, label, parent, tree_method, **filters): tree_method = frappe.get_attr(tree_method) - if not tree_method in frappe.whitelisted: + if tree_method not in frappe.whitelisted: frappe.throw(_("Not Permitted"), frappe.PermissionError) data = tree_method(doctype, parent, **filters) diff --git a/frappe/installer.py b/frappe/installer.py index 0948620ab0..20db451d26 100644 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -184,7 +184,7 @@ def install_app(name, verbose=False, set_as_patched=True): def add_to_installed_apps(app_name, rebuild_website=True): installed_apps = frappe.get_installed_apps() - if not app_name in installed_apps: + if app_name not in installed_apps: installed_apps.append(app_name) frappe.db.set_global("installed_apps", json.dumps(installed_apps)) frappe.db.commit() diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 79be261981..a6b96e8fb5 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -330,7 +330,7 @@ class DatabaseQuery(object): table_name = table_name[7:] if not table_name[0]=='`': table_name = f"`{table_name}`" - if not table_name in self.tables: + if table_name not in self.tables: self.append_table(table_name) def append_table(self, table_name): @@ -428,7 +428,7 @@ class DatabaseQuery(object): f = get_filter(self.doctype, f, additional_filters_config) tname = ('`tab' + f.doctype + '`') - if not tname in self.tables: + if tname not in self.tables: self.append_table(tname) if 'ifnull(' in f.fieldname: diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 6ffaadc5eb..d825261617 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -307,7 +307,7 @@ def get_link_fields(doctype): if not frappe.flags.link_fields: frappe.flags.link_fields = {} - if not doctype in frappe.flags.link_fields: + if doctype not in frappe.flags.link_fields: link_fields = frappe.db.sql("""\ select parent, fieldname, (select issingle from tabDocType dt diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 9ba14d5e68..109260d0fe 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -117,7 +117,7 @@ def get_doc_files(files, start_path): if os.path.isdir(os.path.join(doctype_path, docname)): doc_path = os.path.join(doctype_path, docname, docname) + ".json" if os.path.exists(doc_path): - if not doc_path in files: + if doc_path not in files: files.append(doc_path) return files diff --git a/frappe/realtime.py b/frappe/realtime.py index aa0e2fddad..e0f64d32fb 100644 --- a/frappe/realtime.py +++ b/frappe/realtime.py @@ -65,7 +65,7 @@ def publish_realtime(event=None, message=None, room=None, if after_commit: params = [event, message, room] - if not params in frappe.local.realtime_log: + if params not in frappe.local.realtime_log: frappe.local.realtime_log.append(params) else: emit_via_redis(event, message, room) diff --git a/frappe/test_runner.py b/frappe/test_runner.py index 05f1ce1cd7..20759331c3 100644 --- a/frappe/test_runner.py +++ b/frappe/test_runner.py @@ -285,7 +285,7 @@ def make_test_records(doctype, verbose=0, force=False): if options == "[Select]": continue - if not options in frappe.local.test_objects: + if options not in frappe.local.test_objects: frappe.local.test_objects[options] = [] make_test_records(options, verbose, force) make_test_records_for_doctype(options, verbose, force) @@ -425,7 +425,7 @@ def add_to_test_record_log(doctype): '''Add `doctype` to site/.test_log `.test_log` is a cache of all doctypes for which test records are created''' test_record_log = get_test_record_log() - if not doctype in test_record_log: + if doctype not in test_record_log: frappe.flags.test_record_log.append(doctype) with open(frappe.get_site_path('.test_log'), 'w') as f: f.write('\n'.join(filter(None, frappe.flags.test_record_log))) diff --git a/frappe/translate.py b/frappe/translate.py index c883e63de3..c7293aa9f1 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -135,7 +135,7 @@ def get_dict(fortype, name=None): asset_key = fortype + ":" + (name or "-") translation_assets = cache.hget("translation_assets", frappe.local.lang, shared=True) or {} - if not asset_key in translation_assets: + if asset_key not in translation_assets: messages = [] if fortype=="doctype": messages = get_messages_from_doctype(name) diff --git a/frappe/utils/redis_wrapper.py b/frappe/utils/redis_wrapper.py index 9ca5bbfd4f..c40180b538 100644 --- a/frappe/utils/redis_wrapper.py +++ b/frappe/utils/redis_wrapper.py @@ -154,7 +154,7 @@ class RedisWrapper(redis.Redis): _name = self.make_key(name, shared=shared) # set in local - if not _name in frappe.local.cache: + if _name not in frappe.local.cache: frappe.local.cache[_name] = {} frappe.local.cache[_name][key] = value @@ -173,7 +173,7 @@ class RedisWrapper(redis.Redis): def hget(self, name, key, generator=None, shared=False): _name = self.make_key(name, shared=shared) - if not _name in frappe.local.cache: + if _name not in frappe.local.cache: frappe.local.cache[_name] = {} if not key: return None diff --git a/frappe/utils/user.py b/frappe/utils/user.py old mode 100755 new mode 100644 index cbf38f6acb..ca7a555c72 --- a/frappe/utils/user.py +++ b/frappe/utils/user.py @@ -79,7 +79,7 @@ class UserPermissions: for r in get_valid_perms(): dt = r['parent'] - if not dt in self.perm_map: + if dt not in self.perm_map: self.perm_map[dt] = {} for k in frappe.permissions.rights: diff --git a/frappe/website/utils.py b/frappe/website/utils.py index 152d312533..f0a8da7736 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -226,7 +226,7 @@ def get_full_index(route=None, app=None): # order as per index if present for route, children in children_map.items(): - if not route in pages: + if route not in pages: # no parent (?) continue From be51b60c84be413dda503466c940e6fc3c36efc2 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Mon, 21 Feb 2022 19:56:45 +0100 Subject: [PATCH 50/71] chore: ignore rev in blame --- .git-blame-ignore-revs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index f02694846d..633c5fcfe2 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -13,3 +13,6 @@ fe20515c23a3ac41f1092bf0eaf0a0a452ec2e85 # Updating license headers 34460265554242a8d05fb09f049033b1117e1a2b + +# Refactor "not a in b" -> "a not in b" +745297a49d516e5e3c4bb3e1b0c4235e7d31165d From b2116ff36dff134f711022f1dc44378b3f799f15 Mon Sep 17 00:00:00 2001 From: cpdeethree Date: Mon, 21 Feb 2022 20:20:10 -0600 Subject: [PATCH 51/71] fix: error in table creation from incorrect scope --- frappe/database/postgres/schema.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/database/postgres/schema.py b/frappe/database/postgres/schema.py index 9487bc2fa7..f6235b6ff5 100644 --- a/frappe/database/postgres/schema.py +++ b/frappe/database/postgres/schema.py @@ -19,15 +19,15 @@ class PostgresTable(DBTable): add_text += ",\n".join( ( - "parent varchar({varchar_len})", - "parentfield varchar({varchar_len})", - "parenttype varchar({varchar_len})" + "parent varchar({varchar_len})".format(varchar_len=frappe.db.VARCHAR_LEN), + "parentfield varchar({varchar_len})".format(varchar_len=frappe.db.VARCHAR_LEN), + "parenttype varchar({varchar_len})".format(varchar_len=frappe.db.VARCHAR_LEN) ) ) # TODO: set docstatus length # create table - frappe.db.sql(("""create table `%s` ( + frappe.db.sql("""create table `%s` ( name varchar({varchar_len}) not null primary key, creation timestamp(6), modified timestamp(6), @@ -35,7 +35,7 @@ class PostgresTable(DBTable): owner varchar({varchar_len}), docstatus smallint not null default '0', idx bigint not null default '0', - %s)""" % (self.table_name, add_text)).format(varchar_len=frappe.db.VARCHAR_LEN)) + %s)""".format(varchar_len=frappe.db.VARCHAR_LEN) % (self.table_name, add_text)) self.create_indexes() frappe.db.commit() From dc351b1a82fd32269c070e53d0bc8b0823e1e853 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Tue, 22 Feb 2022 08:29:02 +0530 Subject: [PATCH 52/71] chore: Remove flag from codecov badge To get the actual coverage of the repository --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ef471aa05a..8c8317c8bd 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ - +
From 8e3e0df5d7b99dd4f46eb7355d4c115439406034 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 21 Feb 2022 15:55:30 +0530 Subject: [PATCH 53/71] fix(minor): throttle listview refresh on same args for 3 seconds --- frappe/public/js/frappe/list/base_list.js | 18 +++++++++++++++++- .../public/js/frappe/ui/filters/filter_list.js | 2 +- frappe/public/scss/common/modal.scss | 5 +++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index d09a1c7043..2ea6e55666 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -465,9 +465,14 @@ frappe.views.BaseList = class BaseList { } refresh() { + let args = this.get_call_args(); + if (this.no_change(args)) { + console.log('throttled'); + return Promise.resolve(); + } this.freeze(true); // fetch data from server - return frappe.call(this.get_call_args()).then((r) => { + return frappe.call(args).then((r) => { // render this.prepare_data(r); this.toggle_result_area(); @@ -482,6 +487,17 @@ frappe.views.BaseList = class BaseList { }); } + no_change(args) { + // returns true if arguments are same for the last 5 seconds + // this helps in throttling if called from various sources + if (this.last_args && JSON.stringify(args) === this.last_args) { + return true; + } + this.last_args = JSON.stringify(args); + setTimeout(() => { this.last_args = null}, 3000); + return false; + } + prepare_data(r) { let data = r.message || {}; diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index 1377a6ff13..b0a1eee707 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -293,7 +293,7 @@ frappe.ui.FilterGroup = class {
-
+
diff --git a/frappe/public/scss/common/modal.scss b/frappe/public/scss/common/modal.scss index ec582591f2..c9217a075e 100644 --- a/frappe/public/scss/common/modal.scss +++ b/frappe/public/scss/common/modal.scss @@ -225,6 +225,11 @@ body.modal-open[style^="padding-right"] { } } +// modal is xs (for grids) +.modal .hidden-xs { + display: none !important; +} + .dialog-assignment-row { display: flex; align-items: center; From 5fd693d50ed5542f4fb03846997d6707538b66c5 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 21 Feb 2022 15:56:27 +0530 Subject: [PATCH 54/71] fix(minor): throttle listview refresh on same args for 3 seconds --- frappe/public/js/frappe/list/base_list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index 2ea6e55666..1ba729c6a3 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -488,7 +488,7 @@ frappe.views.BaseList = class BaseList { } no_change(args) { - // returns true if arguments are same for the last 5 seconds + // returns true if arguments are same for the last 3 seconds // this helps in throttling if called from various sources if (this.last_args && JSON.stringify(args) === this.last_args) { return true; From 585086c0a8381fd1eb7b14f1429fd651d9ebc6dc Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 22 Feb 2022 10:20:40 +0530 Subject: [PATCH 55/71] fix: Use meta if parent doctype is same as current doctype --- frappe/core/doctype/doctype/doctype.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 7d75ef7ba9..dca0a05281 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -784,10 +784,12 @@ def validate_links_table_fieldnames(meta): if not meta.links or frappe.flags.in_patch or frappe.flags.in_fixtures: return + fieldnames = tuple(field.fieldname for field in meta.fields) for index, link in enumerate(meta.links, 1): - link_meta = frappe.get_meta(link.link_doctype) if not frappe.get_meta(link.link_doctype).has_field(link.link_fieldname): - message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(index, frappe.bold(link.link_fieldname), frappe.bold(link.link_doctype)) + message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format( + index, frappe.bold(link.link_fieldname), frappe.bold(link.link_doctype) + ) frappe.throw(message, InvalidFieldNameError, _("Invalid Fieldname")) if not link.is_child_table: @@ -801,8 +803,15 @@ def validate_links_table_fieldnames(meta): message = _("Document Links Row #{0}: Table Fieldname is mandatory for internal links").format(index) frappe.throw(message, frappe.ValidationError, _("Table Fieldname Missing")) - if not frappe.get_meta(link.parent_doctype).has_field(link.table_fieldname): - message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(index, frappe.bold(link.table_fieldname), frappe.bold(meta.name)) + if meta.name == link.parent_doctype: + field_exists = link.table_fieldname in fieldnames + else: + field_exists = frappe.get_meta(link.parent_doctype).has_field(link.table_fieldname) + + if not field_exists: + message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format( + index, frappe.bold(link.table_fieldname), frappe.bold(meta.name) + ) frappe.throw(message, frappe.ValidationError, _("Invalid Table Fieldname")) def validate_fields_for_doctype(doctype): From a7cd6003cedfea1b6da2755a171f462103ed1a7f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 22 Feb 2022 10:26:42 +0530 Subject: [PATCH 56/71] fix: Wrap timeout getting for custom queues in function (backport #15933) (#16054) Co-authored-by: Lev Vereshchagin Co-authored-by: Gavin D'souza --- frappe/utils/background_jobs.py | 35 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/frappe/utils/background_jobs.py b/frappe/utils/background_jobs.py index 2c8b7f5fd3..58029dbc5f 100755 --- a/frappe/utils/background_jobs.py +++ b/frappe/utils/background_jobs.py @@ -1,6 +1,7 @@ import os import socket import time +from functools import lru_cache from uuid import uuid4 from collections import defaultdict from typing import List @@ -20,18 +21,22 @@ from frappe.utils.redis_queue import RedisQueue from frappe.utils.commands import log -common_site_config = frappe.get_file_json("common_site_config.json") -custom_workers_config = common_site_config.get("workers", {}) -default_timeout = 300 -queue_timeout = { - "default": default_timeout, - "short": default_timeout, - "long": 1500, - **{ - worker: config.get("timeout", default_timeout) - for worker, config in custom_workers_config.items() + +@lru_cache() +def get_queues_timeout(): + common_site_config = frappe.get_conf() + custom_workers_config = common_site_config.get("workers", {}) + default_timeout = 300 + + return { + "default": default_timeout, + "short": default_timeout, + "long": 1500, + **{ + worker: config.get("timeout", default_timeout) + for worker, config in custom_workers_config.items() + } } -} redis_connection = None @@ -57,7 +62,7 @@ def enqueue(method, queue='default', timeout=None, event=None, q = get_queue(queue, is_async=is_async) if not timeout: - timeout = queue_timeout.get(queue) or 300 + timeout = get_queues_timeout().get(queue) or 300 queue_args = { "site": frappe.local.site, "user": frappe.session.user, @@ -204,7 +209,7 @@ def get_jobs(site=None, queue=None, key='method'): def get_queue_list(queue_list=None, build_queue_name=False): '''Defines possible queues. Also wraps a given queue in a list after validating.''' - default_queue_list = list(queue_timeout) + default_queue_list = list(get_queues_timeout()) if queue_list: if isinstance(queue_list, str): queue_list = [queue_list] @@ -236,7 +241,7 @@ def get_queue(qtype, is_async=True): def validate_queue(queue, default_queue_list=None): if not default_queue_list: - default_queue_list = list(queue_timeout) + default_queue_list = list(get_queues_timeout()) if queue not in default_queue_list: frappe.throw(_("Queue should be one of {0}").format(', '.join(default_queue_list))) @@ -296,7 +301,7 @@ def generate_qname(qtype: str) -> str: def is_queue_accessible(qobj: Queue) -> bool: """Checks whether queue is relate to current bench or not. """ - accessible_queues = [generate_qname(q) for q in list(queue_timeout)] + accessible_queues = [generate_qname(q) for q in list(get_queues_timeout())] return qobj.name in accessible_queues def enqueue_test_job(): From 4cb37c129b76cb0128bd2522e15c8e8f0d3db9ed Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 22 Feb 2022 10:34:38 +0530 Subject: [PATCH 57/71] fix(minor): test and linting --- cypress/integration/list_view.js | 1 + frappe/public/js/frappe/form/grid_row.js | 8 ++++---- frappe/public/js/frappe/list/base_list.js | 6 ++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cypress/integration/list_view.js b/cypress/integration/list_view.js index b161af2df7..b7a96db4bc 100644 --- a/cypress/integration/list_view.js +++ b/cypress/integration/list_view.js @@ -12,6 +12,7 @@ context('List View', () => { cy.get('.list-row-container .list-row-checkbox').click({ multiple: true, force: true }); cy.get('.actions-btn-group button').contains('Actions').should('be.visible'); cy.intercept('/api/method/frappe.desk.reportview.get').as('list-refresh'); + cy.wait(300); // wait before you hit another refresh cy.get('button[data-original-title="Refresh"]').click(); cy.wait('@list-refresh'); cy.get('.list-row-container .list-row-checkbox:checked').should('be.visible'); diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 0ee5a180e0..ec89d26213 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -340,7 +340,7 @@ export default class GridRow {
-

+ ${__('Add / Remove Columns')} @@ -420,10 +420,10 @@ export default class GridRow { data-label='${docfield.label}' data-type='${docfield.fieldtype}'>

-
+ -
+
${__(docfield.label)}
@@ -431,7 +431,7 @@ export default class GridRow { value='${docfield.columns || cint(d.columns)}' data-fieldname='${docfield.fieldname}' style='background-color: #ffff; display: inline'>
-
+
diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index 1ba729c6a3..b7cb5a05df 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -467,7 +467,7 @@ frappe.views.BaseList = class BaseList { refresh() { let args = this.get_call_args(); if (this.no_change(args)) { - console.log('throttled'); + // console.log('throttled'); return Promise.resolve(); } this.freeze(true); @@ -494,7 +494,9 @@ frappe.views.BaseList = class BaseList { return true; } this.last_args = JSON.stringify(args); - setTimeout(() => { this.last_args = null}, 3000); + setTimeout(() => { + this.last_args = null; + }, 3000); return false; } From 33890ed8606cdfcff4920a749e36a57c4661fe4e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 22 Feb 2022 11:23:51 +0530 Subject: [PATCH 58/71] refactor: use f-strings instead of .format and % --- frappe/database/postgres/schema.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/frappe/database/postgres/schema.py b/frappe/database/postgres/schema.py index f6235b6ff5..bb7ff20a26 100644 --- a/frappe/database/postgres/schema.py +++ b/frappe/database/postgres/schema.py @@ -5,29 +5,29 @@ from frappe.database.schema import DBTable, get_definition class PostgresTable(DBTable): def create(self): - add_text = "" + varchar_len = frappe.db.VARCHAR_LEN + additional_definitions = "" # columns column_defs = self.get_column_definitions() if column_defs: - add_text += ",\n".join(column_defs) + additional_definitions += ",\n".join(column_defs) # child table columns if self.meta.get("istable") or 0: if column_defs: - add_text += ",\n" + additional_definitions += ",\n" - add_text += ",\n".join( + additional_definitions += ",\n".join( ( - "parent varchar({varchar_len})".format(varchar_len=frappe.db.VARCHAR_LEN), - "parentfield varchar({varchar_len})".format(varchar_len=frappe.db.VARCHAR_LEN), - "parenttype varchar({varchar_len})".format(varchar_len=frappe.db.VARCHAR_LEN) + f"parent varchar({varchar_len})", + f"parentfield varchar({varchar_len})", + f"parenttype varchar({varchar_len})", ) ) - # TODO: set docstatus length # create table - frappe.db.sql("""create table `%s` ( + frappe.db.sql(f"""create table `{self.table_name}` ( name varchar({varchar_len}) not null primary key, creation timestamp(6), modified timestamp(6), @@ -35,7 +35,9 @@ class PostgresTable(DBTable): owner varchar({varchar_len}), docstatus smallint not null default '0', idx bigint not null default '0', - %s)""".format(varchar_len=frappe.db.VARCHAR_LEN) % (self.table_name, add_text)) + {additional_definitions} + )""" + ) self.create_indexes() frappe.db.commit() From d87915a12e654dc6312cdfb89b2b7835a33a6d87 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 22 Feb 2022 12:01:30 +0530 Subject: [PATCH 59/71] test: docfield with `{defaults}` --- frappe/core/doctype/doctype/test_doctype.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 50882f51bd..9b4f733e7d 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -498,6 +498,13 @@ class TestDocType(unittest.TestCase): self.assertEqual(doc.is_virtual, 1) self.assertFalse(frappe.db.table_exists('Test Virtual Doctype')) + def test_default_fieldname(self): + fields = [{"label": "title", "fieldname": "title", "fieldtype": "Data", "default": "{some_fieldname}"}] + dt = new_doctype("DT with default field", fields=fields) + dt.insert() + + dt.delete() + def new_doctype(name, unique=0, depends_on='', fields=None): doc = frappe.get_doc({ "doctype": "DocType", From f1f6b21cf1e14328cc72be4029aba4f0086a731c Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 22 Feb 2022 12:08:27 +0530 Subject: [PATCH 60/71] fix(minor): styles for editable checks and links --- frappe/public/scss/desk/frappe_datatable.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/scss/desk/frappe_datatable.scss b/frappe/public/scss/desk/frappe_datatable.scss index fb7d795ced..cfec31b72e 100644 --- a/frappe/public/scss/desk/frappe_datatable.scss +++ b/frappe/public/scss/desk/frappe_datatable.scss @@ -58,7 +58,7 @@ } .link-btn { - top: 6px; + top: 0px; } select { @@ -77,7 +77,7 @@ padding: 0; border: var(--dt-focus-border-width) solid #9bccf8; - input { + input[type="text"] { font-size: inherit; height: 27px; From 15c66f69001dbdca249808fdc17db1c8feb59c90 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 22 Feb 2022 12:23:32 +0530 Subject: [PATCH 61/71] fix(style): fix style for inline button in grid --- frappe/public/scss/common/grid.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/scss/common/grid.scss b/frappe/public/scss/common/grid.scss index 1903413fbb..bfce93dbcc 100644 --- a/frappe/public/scss/common/grid.scss +++ b/frappe/public/scss/common/grid.scss @@ -192,7 +192,7 @@ margin-left: var(--margin-xs); button { - height: 27px; + height: 24px; } } From 75b6ee398c4f3e19934d2b05dbe6bc4e94f531f8 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Tue, 22 Feb 2022 12:44:30 +0530 Subject: [PATCH 62/71] fix: `AttributeError` when initialising `FormMeta` --- frappe/desk/form/meta.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index b91dd3d481..fa6a1f313b 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -12,6 +12,15 @@ from frappe.translate import extract_messages_from_code, make_dict_from_messages from frappe.utils import get_html_format +ASSET_KEYS = ( + "__js", "__css", "__list_js", "__calendar_js", "__map_js", + "__linked_with", "__messages", "__print_formats", "__workflow_docs", + "__form_grid_templates", "__listview_template", "__tree_js", + "__dashboard", "__kanban_column_fields", '__templates', + '__custom_js', '__custom_list_js' +) + + def get_meta(doctype, cached=True): # don't cache for developer mode as js files, templates may be edited if cached and not frappe.conf.developer_mode: @@ -34,6 +43,12 @@ class FormMeta(Meta): super(FormMeta, self).__init__(doctype) self.load_assets() + def set(self, key, value, *args, **kwargs): + if key in ASSET_KEYS: + self.__dict__[key] = value + else: + super(FormMeta, self).set(key, value, *args, **kwargs) + def load_assets(self): if self.get('__assets_loaded', False): return @@ -55,11 +70,7 @@ class FormMeta(Meta): def as_dict(self, no_nulls=False): d = super(FormMeta, self).as_dict(no_nulls=no_nulls) - for k in ("__js", "__css", "__list_js", "__calendar_js", "__map_js", - "__linked_with", "__messages", "__print_formats", "__workflow_docs", - "__form_grid_templates", "__listview_template", "__tree_js", - "__dashboard", "__kanban_column_fields", '__templates', - '__custom_js', '__custom_list_js'): + for k in ASSET_KEYS: d[k] = self.get(k) # d['fields'] = d.get('fields', []) @@ -172,7 +183,7 @@ class FormMeta(Meta): WHERE doc_type=%s AND docstatus<2 and disabled=0""", (self.name,), as_dict=1, update={"doctype":"Print Format"}) - self.set("__print_formats", print_formats, as_value=True) + self.set("__print_formats", print_formats) def load_workflows(self): # get active workflow @@ -186,7 +197,7 @@ class FormMeta(Meta): for d in workflow.get("states"): workflow_docs.append(frappe.get_doc("Workflow State", d.state)) - self.set("__workflow_docs", workflow_docs, as_value=True) + self.set("__workflow_docs", workflow_docs) def load_templates(self): @@ -208,7 +219,7 @@ class FormMeta(Meta): for content in self.get("__form_grid_templates").values(): messages = extract_messages_from_code(content) messages = make_dict_from_messages(messages) - self.get("__messages").update(messages, as_value=True) + self.get("__messages").update(messages) def load_dashboard(self): self.set('__dashboard', self.get_dashboard_data()) @@ -224,7 +235,7 @@ class FormMeta(Meta): fields = [x['field_name'] for x in values] fields = list(set(fields)) - self.set("__kanban_column_fields", fields, as_value=True) + self.set("__kanban_column_fields", fields) except frappe.PermissionError: # no access to kanban board pass From 1e48d058b7868387fbfd25b39e43620e0862a780 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 22 Feb 2022 12:51:11 +0530 Subject: [PATCH 63/71] fix(style): add wait for test --- cypress/integration/list_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/list_view.js b/cypress/integration/list_view.js index b7a96db4bc..3e0d1c9d50 100644 --- a/cypress/integration/list_view.js +++ b/cypress/integration/list_view.js @@ -12,7 +12,7 @@ context('List View', () => { cy.get('.list-row-container .list-row-checkbox').click({ multiple: true, force: true }); cy.get('.actions-btn-group button').contains('Actions').should('be.visible'); cy.intercept('/api/method/frappe.desk.reportview.get').as('list-refresh'); - cy.wait(300); // wait before you hit another refresh + cy.wait(3000); // wait before you hit another refresh cy.get('button[data-original-title="Refresh"]').click(); cy.wait('@list-refresh'); cy.get('.list-row-container .list-row-checkbox:checked').should('be.visible'); From 32ca5bf52b1e165737e23a11bfaef17f6946e938 Mon Sep 17 00:00:00 2001 From: lapardnemihk1099 Date: Tue, 22 Feb 2022 15:32:38 +0530 Subject: [PATCH 64/71] fix: formatting and declaration --- frappe/public/js/frappe/form/controls/date.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/date.js b/frappe/public/js/frappe/form/controls/date.js index fa9dc1c99e..48f4f3b5ee 100644 --- a/frappe/public/js/frappe/form/controls/date.js +++ b/frappe/public/js/frappe/form/controls/date.js @@ -158,10 +158,10 @@ frappe.ui.form.ControlDate = class ControlDate extends frappe.ui.form.ControlDat return value; } get_df_options() { - if(!this.df.options) return {}; - - let options = {}; let df_options = this.df.options; + if (!df_options) return {}; + + let options = {}; if (typeof df_options === 'string') { try { options = JSON.parse(df_options); From eaf8327e0daaaf7a38188ee632c14c4547ca0ba6 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 22 Feb 2022 18:43:16 +0530 Subject: [PATCH 65/71] fix: Use correct path of built assets - Get path from bundled_assets --- frappe/translate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/translate.py b/frappe/translate.py index c883e63de3..0a8c6aeaad 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -576,13 +576,15 @@ def get_server_messages(app): def get_messages_from_include_files(app_name=None): """Returns messages from js files included at time of boot like desk.min.js for desk and web""" + from frappe.utils.jinja_globals import bundled_asset messages = [] app_include_js = frappe.get_hooks("app_include_js", app_name=app_name) or [] web_include_js = frappe.get_hooks("web_include_js", app_name=app_name) or [] include_js = app_include_js + web_include_js for js_path in include_js: - relative_path = os.path.join(frappe.local.sites_path, js_path.lstrip('/')) + file_path = bundled_asset(js_path) + relative_path = os.path.join(frappe.local.sites_path, file_path.lstrip('/')) messages_from_file = get_messages_from_file(relative_path) messages.extend(messages_from_file) From a8da557b032d660fce0be41ffde808e6ef8eaf9b Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 22 Feb 2022 22:48:50 +0530 Subject: [PATCH 66/71] fix(minor): test: blur to set value --- cypress/integration/report_view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cypress/integration/report_view.js b/cypress/integration/report_view.js index 4bc5784a53..884ebdc260 100644 --- a/cypress/integration/report_view.js +++ b/cypress/integration/report_view.js @@ -29,6 +29,7 @@ context('Report View', () => { // select the cell cell.dblclick(); cell.get('.dt-cell__edit--col-4').findByRole('checkbox').check({ force: true }); + cy.get('.frappe-list').click(); // click outside cy.wait('@value-update'); @@ -70,4 +71,4 @@ context('Report View', () => { cy.get('.list-paging-area .btn-more').click(); cy.get('.list-paging-area .list-count').should('contain.text', '1000 of'); }); -}); \ No newline at end of file +}); From 2c1ef84a0bff6825c7f7fc3f8b9057d295ce432c Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 22 Feb 2022 23:10:47 +0530 Subject: [PATCH 67/71] fix(minor): test: blur to set value --- cypress/integration/report_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/report_view.js b/cypress/integration/report_view.js index 884ebdc260..6e3a28bbfc 100644 --- a/cypress/integration/report_view.js +++ b/cypress/integration/report_view.js @@ -29,7 +29,7 @@ context('Report View', () => { // select the cell cell.dblclick(); cell.get('.dt-cell__edit--col-4').findByRole('checkbox').check({ force: true }); - cy.get('.frappe-list').click(); // click outside + cy.get('.dt-row-0 > .dt-cell--col-3').click(); // click outside cy.wait('@value-update'); From 466f946da51cd5578dd86a300545e22be92b741c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 23 Feb 2022 08:51:13 +0530 Subject: [PATCH 68/71] fix: Color picker selected color style --- frappe/public/scss/common/color_picker.scss | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frappe/public/scss/common/color_picker.scss b/frappe/public/scss/common/color_picker.scss index 84755beb18..2e53150a41 100644 --- a/frappe/public/scss/common/color_picker.scss +++ b/frappe/public/scss/common/color_picker.scss @@ -94,7 +94,10 @@ .frappe-control[data-fieldtype='Color'] { input { - padding-left: 40px; + padding-left: 38px; + } + .control-input { + position: relative; } .selected-color { cursor: pointer; @@ -103,7 +106,7 @@ border-radius: 5px; background-color: red; position: absolute; - top: calc(50% + 1px); + top: 5px; left: 8px; content: ' '; &.no-value { @@ -113,10 +116,9 @@ } .like-disabled-input { .color-value { - padding-left: 25px; + padding-left: 26px; } .selected-color { - top: 20%; cursor: default; } } From 522fb1d6d11799c0c089f66d605883704f8f2d42 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 23 Feb 2022 10:19:00 +0530 Subject: [PATCH 69/71] fix(translation): Remove duplicate entry --- frappe/translations/de.csv | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/translations/de.csv b/frappe/translations/de.csv index 25a33370dc..9a60c42854 100644 --- a/frappe/translations/de.csv +++ b/frappe/translations/de.csv @@ -3732,7 +3732,6 @@ Dr,Soll, Due Date,Fälligkeitsdatum, Duplicate,Duplizieren, Edit Profile,Profil bearbeiten, -Email,Email, End Time,Endzeit, Enter Value,Wert eingeben, Entity Type,Entitätstyp, @@ -4184,7 +4183,7 @@ Phone Number,Telefonnummer, Linked Documents,Verknüpfte Dokumente, Account SID,Konto-SID, Steps,Schritte, -email,Email, +email,E-Mail, Component,Komponente, Subtitle,Untertitel, Global Defaults,Allgemeine Voreinstellungen, From bedf60280febe7f11e5630574b220b189f5ffb1c Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Wed, 23 Feb 2022 13:21:21 +0530 Subject: [PATCH 70/71] fix(translation): Add all possible views as comment (#16097) --- frappe/public/js/frappe/list/base_list.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index b7cb5a05df..75063cc53f 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -204,6 +204,11 @@ frappe.views.BaseList = class BaseList { }; if (frappe.boot.desk_settings.view_switcher) { + /* @preserve + for translation, don't remove + __("List View") __("Report View") __("Dashboard View") __("Gantt View"), + __("Kanban View") __("Calendar View") __("Image View") __("Inbox View"), + __("Tree View") __("Map View") */ this.views_menu = this.page.add_custom_button_group(__('{0} View', [this.view_name]), icon_map[this.view_name] || 'list'); this.views_list = new frappe.views.ListViewSelect({ From f4518e2cf9d8bdb952dd82160ff0fb8cf9eab792 Mon Sep 17 00:00:00 2001 From: HarryPaulo Date: Wed, 23 Feb 2022 05:19:40 -0300 Subject: [PATCH 71/71] fix(print): Added properties page-width, page-height (#16045) --- frappe/utils/pdf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index 9a7c0889b5..b8e684869e 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -155,7 +155,7 @@ def read_options_from_html(html): toggle_visible_pdf(soup) # use regex instead of soup-parser - for attr in ("margin-top", "margin-bottom", "margin-left", "margin-right", "page-size", "header-spacing", "orientation"): + for attr in ("margin-top", "margin-bottom", "margin-left", "margin-right", "page-size", "header-spacing", "orientation", "page-width", "page-height"): try: pattern = re.compile(r"(\.print-format)([\S|\s][^}]*?)(" + str(attr) + r":)(.+)(mm;)") match = pattern.findall(html)