diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 7a8563be12..5d2269e1ea 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,40 @@ 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) + + columns = get_columns_dict(data.columns) + content = [] + for col in columns.values(): + content.append(col["label"]) + + 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() + 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 +201,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 +225,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 @@ -297,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 725f495361..9225b8ed92 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,32 @@ def export_query(): for i, row in enumerate(ret): data.append([i+1] + list(row)) - # convert to csv - from cStringIO import StringIO - import csv + if file_format_type == "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)) + # convert to csv + import csv + from cStringIO import StringIO + + 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 == "Excel": + + from frappe.utils.xlsxutils import make_xlsx + xlsx_file = make_xlsx(data, doctype) + + frappe.response['filename'] = doctype + '.xlsx' + frappe.response['filecontent'] = xlsx_file.getvalue() + frappe.response['type'] = 'binary' - f.seek(0) - frappe.response['result'] = unicode(f.read(), 'utf-8') - frappe.response['type'] = 'csv' - frappe.response['doctype'] = doctype def append_totals_row(data): if not data: 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 8454049546..61313e13b4 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 Type"), fieldname:"file_format_type", + options:"Excel\nCSV", default:"Excel", 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: " + me.doctype), __("Download")); + }, true); }, 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: 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 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