From cacc0b8cb25bfe8adee0abf77b735b8647e1b205 Mon Sep 17 00:00:00 2001 From: Achilles Rasquinha Date: Wed, 28 Feb 2018 17:07:59 +0530 Subject: [PATCH 1/4] Use range instead of xrange, and sorted fix (#5094) * Use range instead of xrange * sort using cmp_to_key * [min] import functools --- frappe/core/page/desktop/desktop.py | 3 ++- frappe/utils/goal.py | 2 +- frappe/website/utils.py | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/frappe/core/page/desktop/desktop.py b/frappe/core/page/desktop/desktop.py index f83e37b98b..2824fb7e9b 100644 --- a/frappe/core/page/desktop/desktop.py +++ b/frappe/core/page/desktop/desktop.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import functools import frappe @frappe.whitelist() @@ -20,4 +21,4 @@ def get_help_messages(): for fn in frappe.get_hooks('get_help_messages'): messages += frappe.get_attr(fn)() - return sorted(messages, lambda a, b: cmp(a.get('count'), b.get('count'))) + return sorted(messages, key = functools.cmp_to_key(lambda a, b: cmp(a.get('count'), b.get('count')))) diff --git a/frappe/utils/goal.py b/frappe/utils/goal.py index 95d71cc19a..3e45c11963 100644 --- a/frappe/utils/goal.py +++ b/frappe/utils/goal.py @@ -81,7 +81,7 @@ def get_monthly_goal_graph_data(title, doctype, docname, goal_value_field, goal_ months_formatted = [] values = [] values_formatted = [] - for i in xrange(0, 12): + for i in range(0, 12): month_value = formatdate(add_months(today(), -i), "MM-yyyy") month_word = getdate(month_value).strftime('%b') month_year = getdate(month_value).strftime('%B') + ', ' + getdate(month_value).strftime('%Y') diff --git a/frappe/website/utils.py b/frappe/website/utils.py index 9388116c6a..b784a0ea3d 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -2,6 +2,7 @@ # MIT License. See license.txt from __future__ import unicode_literals +import functools import frappe, re, os from six import iteritems @@ -257,8 +258,8 @@ def get_full_index(route=None, app=None): added.append(child_route) # add remaining pages not in index.txt - _children = sorted(children, lambda a, b: cmp( - os.path.basename(a.route), os.path.basename(b.route))) + _children = sorted(children, key = functools.cmp_to_key(lambda a, b: cmp( + os.path.basename(a.route), os.path.basename(b.route)))) for child_route in _children: if child_route not in new_children: From 3a90ba61e9e5e7fa7da69848c7b5adb87e0bcfbf Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 28 Feb 2018 17:08:46 +0530 Subject: [PATCH 2/4] [minor] Hide add new row button if cannot_add_rows is enabled in df (#5091) --- frappe/public/js/frappe/form/grid.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index e42e9df15c..c4f6e832d0 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -234,7 +234,7 @@ frappe.ui.form.Grid = Class.extend({ this.wrapper.find(".grid-footer").toggle(true); // show, hide buttons to add rows - if(this.cannot_add_rows) { + if(this.cannot_add_rows || (this.df && this.df.cannot_add_rows)) { // add 'hide' to buttons this.wrapper.find(".grid-add-row, .grid-add-multiple-rows") .addClass('hide'); From cd54b6f6a171833c733a5ea6b180d445548df93d Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 28 Feb 2018 17:10:09 +0530 Subject: [PATCH 3/4] Query Report (#5097) * [listview] Show freeze message * Call refresh on same page * Simplify query_report lifecycle, Print partially working * PDF Print, frappe.render_pdf * Show freeze message * Export --- frappe/desk/page/activity/activity.js | 4 + frappe/public/js/frappe/list/base_list.js | 2 +- frappe/public/js/frappe/list/list_view.js | 5 + frappe/public/js/frappe/model/model.js | 38 +-- .../js/frappe/views/reports/query_report.js | 274 +++++++++++++----- frappe/public/js/lib/microtemplate.js | 30 +- frappe/utils/pdf.py | 2 +- yarn.lock | 2 +- 8 files changed, 257 insertions(+), 100 deletions(-) diff --git a/frappe/desk/page/activity/activity.js b/frappe/desk/page/activity/activity.js index 1e623a8f58..86915dcf60 100644 --- a/frappe/desk/page/activity/activity.js +++ b/frappe/desk/page/activity/activity.js @@ -166,6 +166,10 @@ frappe.activity.render_heatmap = function(page) { } frappe.views.Activity = class Activity extends frappe.views.BaseList { + constructor(opts) { + super(opts); + this.show(); + } setup_defaults() { super.setup_defaults(); diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index d17b17b012..ebce0101c5 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -3,7 +3,6 @@ frappe.provide('frappe.views'); frappe.views.BaseList = class BaseList { constructor(opts) { Object.assign(this, opts); - this.show(); } show() { @@ -152,6 +151,7 @@ frappe.views.BaseList = class BaseList { } set_menu_items() { + this.page.clear_menu(); const $secondary_action = this.page.set_secondary_action( this.secondary_action.label, this.secondary_action.action, diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 926d089907..fc2ea761df 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -15,6 +15,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { return false; } + constructor(opts) { + super(opts); + this.show(); + } + show() { this.init().then(() => { if (frappe.route_options) { diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index 0ca8cce86e..039a053fbe 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -154,23 +154,27 @@ $.extend(frappe.model, { }, with_doc: function(doctype, name, callback) { - if(!name) name = doctype; // single type - if(locals[doctype] && locals[doctype][name] && frappe.model.get_docinfo(doctype, name)) { - callback(name); - } else { - return frappe.call({ - method: 'frappe.desk.form.load.getdoc', - type: "GET", - args: { - doctype: doctype, - name: name - }, - freeze: true, - callback: function(r) { - callback && callback(name, r); - } - }); - } + return new Promise(resolve => { + if(!name) name = doctype; // single type + if(locals[doctype] && locals[doctype][name] && frappe.model.get_docinfo(doctype, name)) { + callback && callback(name); + resolve(frappe.get_doc(doctype, name)); + } else { + return frappe.call({ + method: 'frappe.desk.form.load.getdoc', + type: "GET", + args: { + doctype: doctype, + name: name + }, + freeze: true, + callback: function(r) { + callback && callback(name, r); + resolve(frappe.get_doc(doctype, name)); + } + }); + } + }); }, get_docinfo: function(doctype, name) { diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 91f73bf6ea..1fc5051d8c 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -19,25 +19,24 @@ frappe.standard_pages["query-report"] = function() { }); $(wrapper).bind("show", function() { - frappe.query_report.init(); + frappe.query_report.show(); }); }; frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { show() { - + this.init().then(() => this.load()); } init() { - if (this.init_promise && frappe.get_route()[1] === this.report_name) { + if (this.init_promise) { return this.init_promise; } let tasks = [ this.setup_defaults, this.setup_page, - this.setup_report_wrapper, - this.setup_report + this.setup_report_wrapper ].map(fn => fn.bind(this)); this.init_promise = frappe.run_serially(tasks); @@ -47,17 +46,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { setup_defaults() { this.route = frappe.get_route(); this.page_name = frappe.get_route_str(); - this.report_name = this.route[1]; - this.page_title = __(this.report_name); - this.user_settings = frappe.get_user_settings(this.report_name); - - this.start = 0; - this.page_length = 20; - this.data = []; - - this.fields = []; - this.filters = []; - this.order_by = 'modified desc'; // Setup buttons this.primary_action = null; @@ -66,43 +54,58 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { action: () => this.refresh() }; + // throttle refresh for 300ms + this.refresh = frappe.utils.throttle(this.refresh, 300); + + this.menu_items = []; + } + + load() { + this.toggle_freeze(true); + if (this.report_name !== frappe.get_route()[1]) { + // different report + this.load_report(); + } else { + // same report + this.refresh_report(); + } + } + + load_report() { + this.route = frappe.get_route(); + this.page_name = frappe.get_route_str(); + this.report_name = this.route[1]; + this.page_title = __(this.report_name); + this.user_settings = frappe.get_user_settings(this.report_name); this.menu_items = this.get_menu_items(); + this.datatable = null; - // throttle refresh for 500ms - this.refresh = frappe.utils.throttle(this.refresh, 500); + frappe.run_serially([ + () => this.get_report_doc(), + () => this.get_report_settings(), + () => this.report_settings.onload && this.report_settings.onload(this), + () => this.setup_page_head(), + () => this.refresh_report() + ]); + } - // Report Doc - return frappe.db.get_doc('Report', this.report_name) + refresh_report() { + return frappe.run_serially([ + () => this.setup_filters(), + () => this.set_route_filters(), + () => this.refresh() + ]); + } + + get_report_doc() { + return frappe.model.with_doc('Report', this.report_name) .then(doc => { this.report_doc = doc; }) - // Ref DocType - .then(() => { - if (this.report_doc.ref_doctype) { - return frappe.model.with_doctype(this.report_doc.ref_doctype); - } - return Promise.resolve(); - }); + .then(() => frappe.model.with_doc('DocType', this.report_doc.ref_doctype)); } - setup_report_wrapper() { - if (this.$report) return; - this.$report = $('
').appendTo(this.page.main); - } - - setup_page_head() { - super.setup_page_head(); - this.page.set_title_sub(``); - } - - setup_report() { - this.$report.empty(); - this.datatable = null; - return this.load_report_script() - .then(() => this.load_report()); - } - - load_report_script() { + get_report_settings() { if (frappe.query_reports[this.report_name]) { this.report_settings = frappe.query_reports[this.report_name]; return this._load_script; @@ -126,20 +129,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { return this._load_script; } - load_report() { - this.page.clear_inner_toolbar(); - this.setup_filters(); - // this.toggle_expand_collapse_buttons(false); - // this.is_tree_report = false; - - if (this.report_settings.onload) { - frappe.run_serially([ - () => this.report_settings.onload(this), - () => this.refresh() - ]); - } - } - setup_filters() { this.clear_filters(); const { filters = [] } = this.report_settings; @@ -181,8 +170,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { // as they can be used in // setting/triggering the filters this.set_filters_by_name(); - - return this.set_route_filters(); } set_filters_by_name() { @@ -217,27 +204,23 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { refresh() { const filters = this.get_filter_values(true); - - if (this.report_ajax) { - // abort previous request - this.report_ajax.abort(); - } - - this.report_ajax = frappe.call({ + return new Promise(resolve => frappe.call({ method: "frappe.desk.query_report.run", type: "GET", args: { report_name: this.report_name, filters: filters - } - }).then(r => { - this.report_ajax = undefined; + }, + callback: resolve + })).then(r => { this.render_report(r.message); }); } render_report(data) { + this.toggle_freeze(false); this._data = data.result; + this._columns = data.columns; if (this.datatable) { this.datatable.refresh(data.result); return; @@ -339,11 +322,111 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } set_breadcrumbs() { - if (!this.report_doc.ref_doctype) return; + if (!this.report_doc || !this.report_doc.ref_doctype) return; const ref_doctype = frappe.get_meta(this.report_doc.ref_doctype); frappe.breadcrumbs.add(ref_doctype.module); } + print_report(print_settings) { + frappe.render_grid({ + template: this.html_format || null, + title: __(this.report_name), + print_settings: print_settings, + filters: this.get_filter_values(), + data: this.get_data_for_print(), + columns: this.get_columns_for_print(), + report: this + }); + } + + pdf_report(print_settings) { + const base_url = frappe.urllib.get_base_url(); + const print_css = frappe.boot.print_css; + const landscape = print_settings.orientation == "Landscape"; + const columns = this.columns; + + let html; + if (this.html_format) { + const content = frappe.render(this.html_format, { + data: this.get_data_for_print(), + filters: this.get_filter_values(), + report: this, + }); + + //Render Report in HTML + html = frappe.render_template("print_template", { + title:__(this.report_name), + content: content, + base_url: base_url, + print_css: print_css, + print_settings: print_settings, + landscape: landscape, + columns: columns + }); + } else { + const content = frappe.render_template("print_grid", { + title: __(this.report_name), + data: this.get_data_for_print(), + columns: columns + }) + + //Render Report in HTML + html = frappe.render_template("print_template", { + content: content, + title: __(this.report_name), + base_url: base_url, + print_css: print_css, + print_settings: print_settings, + landscape: landscape, + columns: columns + }); + } + + frappe.render_pdf(html, print_settings); + } + + export_report() { + if (this.export_dialog) { + this.export_dialog.clear(); + this.export_dialog.show(); + return; + } + + this.export_dialog = frappe.prompt({ + label: __('Select File Format'), + fieldname: 'file_format', + fieldtype: 'Select', + options: ['Excel', 'CSV'], + default: 'Excel', + reqd: 1 + }, ({ file_format }) => { + if (file_format === 'CSV') { + frappe.tools.downloadify(this.get_data_for_print(), null, this.report_name); + } else { + const filters = this.get_filter_values(true); + + const args = { + cmd: 'frappe.desk.query_report.export_query', + report_name: this.report_name, + file_format_type: file_format, + filters: filters, + visible_idx: this.datatable.datamanager.getFilteredRowIndices(), + }; + + open_url_post(frappe.request.url, args); + } + }, __("Export Report: "+ this.report_name), __("Download")); + } + + get_data_for_print() { + const indices = this.datatable.datamanager.getFilteredRowIndices(); + return indices.map(i => this._data[i]); + } + + get_columns_for_print() { + return this._columns || []; + } + get_menu_items() { return [ { @@ -360,24 +443,30 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { { label: __('Print'), action: () => { - frappe.ui.get_print_settings(false, function(print_settings) { - this.print_report(print_settings); - }, this.report_doc.letter_head); + frappe.ui.get_print_settings( + false, + print_settings => this.print_report(print_settings), + this.report_doc.letter_head + ); }, + condition: () => frappe.model.can_print(this.report_doc.ref_doctype), standard: true }, { label: __('PDF'), action: () => { - frappe.ui.get_print_settings(false, function(print_settings) { - this.pdf_report(print_settings); - }, this.report_doc.letter_head); + frappe.ui.get_print_settings( + false, + print_settings => this.pdf_report(print_settings), + this.report_doc.letter_head + ); }, + condition: () => frappe.model.can_print(this.report_doc.ref_doctype), standard: true }, { label: __('Export'), - action: () => this.make_export(), + action: () => this.export_report(), standard: true }, { @@ -401,4 +490,31 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { }, ]; } + + setup_page_head() { + super.setup_page_head(); + this.page.set_title_sub(``); + } + + setup_report_wrapper() { + if (this.$report) return; + this.$report = $('
').appendTo(this.page.main); + this.$freeze = + $(`
+
${__('Loading')}...
+
`).hide().appendTo(this.page.main); + } + + toggle_freeze(flag) { + this.$freeze.toggle(flag); + this.$report.toggle(!flag); + } + + get data() { + return this._data; + } + + get columns() { + return this._columns; + } }; \ No newline at end of file diff --git a/frappe/public/js/lib/microtemplate.js b/frappe/public/js/lib/microtemplate.js index 6aa85c1947..9d75ee33fa 100644 --- a/frappe/public/js/lib/microtemplate.js +++ b/frappe/public/js/lib/microtemplate.js @@ -104,7 +104,7 @@ frappe.render_grid = function(opts) { // render content if(!opts.content) { - opts.content = frappe.render_template("print_grid", opts); + opts.content = frappe.render_template(opts.template || "print_grid", opts); } // render HTML wrapper page @@ -135,3 +135,31 @@ frappe.render_tree = function(opts) { w.document.write(tree); w.document.close(); } +frappe.render_pdf = function(html, opts = {}) { + //Create a form to place the HTML content + var formData = new FormData(); + + //Push the HTML content into an element + formData.append("html", html); + if (opts.orientation) { + formData.append("orientation", opts.orientation); + } + var blob = new Blob([], { type: "text/xml"}); + formData.append("blob", blob); + + var xhr = new XMLHttpRequest(); + xhr.open("POST", '/api/method/frappe.utils.print_format.report_to_pdf'); + xhr.setRequestHeader("X-Frappe-CSRF-Token", frappe.csrf_token); + xhr.responseType = "arraybuffer"; + + xhr.onload = function(success) { + if (this.status === 200) { + var blob = new Blob([success.currentTarget.response], {type: "application/pdf"}); + var objectUrl = URL.createObjectURL(blob); + + //Open report in a new window + window.open(objectUrl); + } + }; + xhr.send(formData); +} \ No newline at end of file diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index 57c4051be6..e15e4d4e23 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -128,7 +128,7 @@ def prepare_header_footer(soup): # create temp file fname = os.path.join("/tmp", "frappe-pdf-{0}.html".format(frappe.generate_hash())) - with open(fname, "w") as f: + with open(fname, "wb") as f: f.write(html.encode("utf-8")) # {"header-html": "/tmp/frappe-pdf-random.html"} diff --git a/yarn.lock b/yarn.lock index 9f9d8c5f7f..a58c5defd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -612,7 +612,7 @@ forwarded@~0.1.2: frappe-datatable@frappe/datatable: version "0.0.2" - resolved "https://codeload.github.com/frappe/datatable/tar.gz/5b8be4c5b09a243ed8580cb9667d7658ca249f02" + resolved "https://codeload.github.com/frappe/datatable/tar.gz/99701f2477b3fb8180ccafaf2c2746886b13ba53" dependencies: clusterize.js "^0.18.0" lodash "^4.17.5" From 3666513113b904ab6f43652544385f3d8edba0ff Mon Sep 17 00:00:00 2001 From: Ameya Shenoy Date: Wed, 28 Feb 2018 17:35:04 +0530 Subject: [PATCH 4/4] Update hooks.py (#5099) --- frappe/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/hooks.py b/frappe/hooks.py index ea57229c15..1d02b64ac2 100755 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -11,7 +11,7 @@ app_color = "orange" source_link = "https://github.com/frappe/frappe" app_license = "MIT" -develop_version = '10.x.x-develop' +develop_version = '11.x.x-develop' app_email = "info@frappe.io"