From 6cdf8ec86c9ed456004bd4f5cae77ca423143769 Mon Sep 17 00:00:00 2001 From: Manas Solanki Date: Thu, 2 Feb 2017 13:09:21 +0530 Subject: [PATCH 1/5] Export Reports in Excel file format --- frappe/desk/reportview.py | 50 ++++++++++++++----- .../js/frappe/views/reports/reportview.js | 22 ++++++-- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 725f495361..e9b761df79 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -91,8 +91,10 @@ def export_query(): form_params["as_list"] = True doctype = form_params.doctype add_totals_row = None + file_format_type = form_params["file_format_type"] del form_params["doctype"] + del form_params["file_format_type"] if 'add_totals_row' in form_params and form_params['add_totals_row']=='1': add_totals_row = 1 @@ -110,20 +112,42 @@ def export_query(): for i, row in enumerate(ret): data.append([i+1] + list(row)) - # convert to csv + from cStringIO import StringIO - import csv - - f = StringIO() - writer = csv.writer(f) - for r in data: - # encode only unicode type strings and not int, floats etc. - writer.writerow(map(lambda v: isinstance(v, unicode) and v.encode('utf-8') or v, r)) - - f.seek(0) - frappe.response['result'] = unicode(f.read(), 'utf-8') - frappe.response['type'] = 'csv' - frappe.response['doctype'] = doctype + + if file_format_type == "csv Format": + + # convert to csv + import csv + + f = StringIO() + writer = csv.writer(f) + for r in data: + # encode only unicode type strings and not int, floats etc. + writer.writerow(map(lambda v: isinstance(v, unicode) and v.encode('utf-8') or v, r)) + + f.seek(0) + frappe.response['result'] = unicode(f.read(), 'utf-8') + frappe.response['type'] = 'csv' + frappe.response['doctype'] = doctype + + elif file_format_type == "xlsx Format": + + # convert to xlsx + import openpyxl + + wb = openpyxl.Workbook() + ws = wb.active + for row in data: + ws.append(row) + + xlsx_file = StringIO() + wb.save(xlsx_file) + + frappe.response['filename'] = doctype + '.xlsx' + frappe.response['filecontent'] = xlsx_file.getvalue() + frappe.response['type'] = 'binary' + def append_totals_row(data): if not data: diff --git a/frappe/public/js/frappe/views/reports/reportview.js b/frappe/public/js/frappe/views/reports/reportview.js index 8454049546..37706a7f4f 100644 --- a/frappe/public/js/frappe/views/reports/reportview.js +++ b/frappe/public/js/frappe/views/reports/reportview.js @@ -265,6 +265,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ filters: this.filter_list.get_filters(), save_list_settings_fields: 1, with_childnames: 1, + file_format_type: this.file_format_type } }, @@ -676,11 +677,22 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ } var export_btn = this.page.add_menu_item(__('Export'), function() { var args = me.get_args(); - args.cmd = 'frappe.desk.reportview.export_query' - if(me.add_totals_row) { - args.add_totals_row = 1; - } - open_url_post(frappe.request.url, args); + + frappe.prompt({fieldtype:"Select", label: __("Select File Format"), fieldname:"file_format_type", + options:"xlsx Format\ncsv Format", default:"xlsx Format", reqd: 1}, + function(data) { + + args.cmd = 'frappe.desk.reportview.export_query'; + args.file_format_type = data.file_format_type; + + if(me.add_totals_row) { + args.add_totals_row = 1; + } + + open_url_post(frappe.request.url, args); + + }, __("Export Report Data"), __("Download")); + }, true); }, From 001e2d18e94aeee4e0fab10fe6e90f4c5693cb51 Mon Sep 17 00:00:00 2001 From: Manas Solanki Date: Wed, 8 Feb 2017 18:04:00 +0530 Subject: [PATCH 2/5] Add openpyxl in requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 35e51022d2..ddd3aaba0e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,3 +41,4 @@ xlwt oauthlib PyJWT pypdf +openpyxl \ No newline at end of file From 59917bc2af288aad96c2460003a746b463876740 Mon Sep 17 00:00:00 2001 From: Manas Solanki Date: Wed, 8 Feb 2017 16:23:15 +0530 Subject: [PATCH 3/5] Query Report export in excel file format --- frappe/desk/query_report.py | 41 ++++++++++++++++ frappe/desk/reportview.py | 4 +- .../js/frappe/views/reports/query_report.js | 47 +++++++++++++++---- .../js/frappe/views/reports/reportview.js | 6 +-- 4 files changed, 85 insertions(+), 13 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 7a8563be12..2c90a215dc 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -61,6 +61,7 @@ def get_script(report_name): @frappe.whitelist() def run(report_name, filters=None, user=None): + report = get_report_doc(report_name) if not user: user = frappe.session.user @@ -111,6 +112,44 @@ def run(report_name, filters=None, user=None): "chart": chart } + +@frappe.whitelist() +def export_query(): + """export from query reports""" + + data = frappe._dict(frappe.local.form_dict) + + del data["cmd"] + + if isinstance(data.get("filters"), basestring): + filters = json.loads(data["filters"]) + if isinstance(data.get("report_name"), basestring): + report_name = data["report_name"] + if isinstance(data.get("file_format_type"), basestring): + file_format_type = data["file_format_type"] + + if file_format_type == "Excel": + data = run(report_name, filters) + data = frappe._dict(data) + + # convert to xlsx + import openpyxl + from cStringIO import StringIO + + wb = openpyxl.Workbook() + ws = wb.active + ws.append(data.columns) + for row in data.result: + ws.append(row) + + xlsx_file = StringIO() + wb.save(xlsx_file) + + frappe.response['filename'] = report_name + '.xlsx' + frappe.response['filecontent'] = xlsx_file.getvalue() + frappe.response['type'] = 'binary' + + def get_report_module_dotted_path(module, report_name): return frappe.local.module_app[scrub(module)] + "." + scrub(module) \ + ".report." + scrub(report_name) + "." + scrub(report_name) @@ -166,6 +205,7 @@ def add_total_row(result, columns, meta = None): result.append(total_row) return result + def get_filtered_data(ref_doctype, columns, data, user): result = [] linked_doctypes = get_linked_doctypes(columns, data) @@ -189,6 +229,7 @@ def get_filtered_data(ref_doctype, columns, data, user): return result + def has_match(row, linked_doctypes, doctype_match_filters, ref_doctype, if_owner, columns_dict, user): """Returns True if after evaluating permissions for each linked doctype - There is an owner match for the ref_doctype diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index e9b761df79..b0473cd378 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -115,7 +115,7 @@ def export_query(): from cStringIO import StringIO - if file_format_type == "csv Format": + if file_format_type == "CSV": # convert to csv import csv @@ -131,7 +131,7 @@ def export_query(): frappe.response['type'] = 'csv' frappe.response['doctype'] = doctype - elif file_format_type == "xlsx Format": + elif file_format_type == "Excel": # convert to xlsx import openpyxl diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 2e041c904f..ae9409cb04 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -88,7 +88,7 @@ frappe.views.QueryReport = Class.extend({ }, me.report_doc.letter_head); }, true); - this.page.add_menu_item(__('Export'), function() { me.export_report(); }, + this.page.add_menu_item(__('Export'), function() { me.make_export(); }, true); this.page.add_menu_item(__("Setup Auto Email"), function() { @@ -784,18 +784,49 @@ frappe.views.QueryReport = Class.extend({ } }); }, - export_report: function() { + + make_export: function() { + + var me = this; + this.title = this.report_name; + if(!frappe.model.can_export(this.report_doc.ref_doctype)) { msgprint(__("You are not allowed to export this report")); return false; } - var result = $.map(frappe.slickgrid_tools.get_view_data(this.columns, this.dataView), - function(row) { - return [row.splice(1)]; - }); - this.title = this.report_name; - frappe.tools.downloadify(result, null, this.title); + frappe.prompt({fieldtype:"Select", label: __("Select File Type"), fieldname:"file_format_type", + options:"Excel\nCSV", default:"Excel", reqd: 1}, + function(data) { + + if (data.file_format_type == "CSV") { + + var result = $.map(frappe.slickgrid_tools.get_view_data(me.columns, me.dataView), + function(row) { + return [row.splice(1)]; + }); + frappe.tools.downloadify(result, null, me.title); + } + + else if (data.file_format_type == "Excel") { + + me.wrapper.find(".results").toggle(false); + try { + var filters = me.get_values(true); + } catch(e) { + return; + } + var args = { + cmd: 'frappe.desk.query_report.export_query', + report_name: me.report_name, + file_format_type: data.file_format_type, + filters: filters + }; + + open_url_post(frappe.request.url, args); + } + }, __("Export Report: "+ me.title), __("Download")); + return false; }, diff --git a/frappe/public/js/frappe/views/reports/reportview.js b/frappe/public/js/frappe/views/reports/reportview.js index 37706a7f4f..61313e13b4 100644 --- a/frappe/public/js/frappe/views/reports/reportview.js +++ b/frappe/public/js/frappe/views/reports/reportview.js @@ -678,8 +678,8 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ var export_btn = this.page.add_menu_item(__('Export'), function() { var args = me.get_args(); - frappe.prompt({fieldtype:"Select", label: __("Select File Format"), fieldname:"file_format_type", - options:"xlsx Format\ncsv Format", default:"xlsx Format", reqd: 1}, + frappe.prompt({fieldtype:"Select", label: __("Select File Type"), fieldname:"file_format_type", + options:"Excel\nCSV", default:"Excel", reqd: 1}, function(data) { args.cmd = 'frappe.desk.reportview.export_query'; @@ -691,7 +691,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({ open_url_post(frappe.request.url, args); - }, __("Export Report Data"), __("Download")); + }, __("Export Report: " + me.doctype), __("Download")); }, true); }, From 8bcd4798312eba2b9dcb37fadda2627dd1026b9c Mon Sep 17 00:00:00 2001 From: Manas Solanki Date: Fri, 10 Feb 2017 17:13:01 +0530 Subject: [PATCH 4/5] Add binary mime type in response.py --- frappe/utils/response.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/frappe/utils/response.py b/frappe/utils/response.py index e5de1feea9..0417a38551 100644 --- a/frappe/utils/response.py +++ b/frappe/utils/response.py @@ -36,7 +36,8 @@ def build_response(response_type=None): 'download': as_raw, 'json': as_json, 'page': as_page, - 'redirect': redirect + 'redirect': redirect, + 'binary': as_binary } return response_type_map[frappe.response.get('type') or response_type]() @@ -68,6 +69,13 @@ def as_json(): response.data = json.dumps(frappe.local.response, default=json_handler, separators=(',',':')) return response +def as_binary(): + response = Response() + response.mimetype = 'application/octet-stream' + response.headers[b"Content-Disposition"] = ("filename=\"%s\"" % frappe.response['filename'].replace(' ', '_')).encode("utf-8") + response.data = frappe.response['filecontent'] + return response + def make_logs(response = None): """make strings for msgprint and errprint""" if not response: From a9d5715f8abca4ab201c64ddeef9c558986136c3 Mon Sep 17 00:00:00 2001 From: Manas Solanki Date: Mon, 13 Feb 2017 13:13:46 +0530 Subject: [PATCH 5/5] Fix for opening the file in mac and some styling in excel file --- frappe/desk/query_report.py | 19 ++++++++----------- frappe/desk/reportview.py | 16 +++------------- frappe/utils/xlsxutils.py | 26 ++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 24 deletions(-) create mode 100644 frappe/utils/xlsxutils.py diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 2c90a215dc..5d2269e1ea 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -129,21 +129,17 @@ def export_query(): file_format_type = data["file_format_type"] if file_format_type == "Excel": + data = run(report_name, filters) data = frappe._dict(data) - # convert to xlsx - import openpyxl - from cStringIO import StringIO - - wb = openpyxl.Workbook() - ws = wb.active - ws.append(data.columns) - for row in data.result: - ws.append(row) + columns = get_columns_dict(data.columns) + content = [] + for col in columns.values(): + content.append(col["label"]) - xlsx_file = StringIO() - wb.save(xlsx_file) + from frappe.utils.xlsxutils import make_xlsx + xlsx_file = make_xlsx([content] + data.result, "Query Report") frappe.response['filename'] = report_name + '.xlsx' frappe.response['filecontent'] = xlsx_file.getvalue() @@ -338,6 +334,7 @@ def get_columns_dict(columns): else: col_dict["fieldtype"] = col[1] + col_dict["label"] = col[0] col_dict["fieldname"] = frappe.scrub(col[0]) # dict diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index b0473cd378..9225b8ed92 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -112,13 +112,11 @@ def export_query(): for i, row in enumerate(ret): data.append([i+1] + list(row)) - - from cStringIO import StringIO - if file_format_type == "CSV": # convert to csv import csv + from cStringIO import StringIO f = StringIO() writer = csv.writer(f) @@ -133,16 +131,8 @@ def export_query(): elif file_format_type == "Excel": - # convert to xlsx - import openpyxl - - wb = openpyxl.Workbook() - ws = wb.active - for row in data: - ws.append(row) - - xlsx_file = StringIO() - wb.save(xlsx_file) + from frappe.utils.xlsxutils import make_xlsx + xlsx_file = make_xlsx(data, doctype) frappe.response['filename'] = doctype + '.xlsx' frappe.response['filecontent'] = xlsx_file.getvalue() diff --git a/frappe/utils/xlsxutils.py b/frappe/utils/xlsxutils.py new file mode 100644 index 0000000000..0d6e2e797b --- /dev/null +++ b/frappe/utils/xlsxutils.py @@ -0,0 +1,26 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt +from __future__ import unicode_literals + +import frappe +from frappe.utils import encode, cstr, cint, flt, comma_or + +import openpyxl +from cStringIO import StringIO +from openpyxl.styles import Font + +# return xlsx file object +def make_xlsx(data, sheet_name): + + wb = openpyxl.Workbook(write_only=True) + ws = wb.create_sheet(sheet_name, 0) + + row1 = ws.row_dimensions[1] + row1.font = Font(name='Calibri',bold=True) + + for row in data: + ws.append(row) + + xlsx_file = StringIO() + wb.save(xlsx_file) + return xlsx_file \ No newline at end of file