Merge pull request #26163 from barredterra/csv-decimal-sep

feat(CSV export): allow custom decimal separator
This commit is contained in:
Akhil Narang 2024-04-25 16:55:55 +05:30 committed by GitHub
commit e455b7b452
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 46 additions and 3 deletions

View file

@ -36,6 +36,7 @@ def pop_csv_params(form_dict):
return {
"delimiter": cstr(form_dict.pop("csv_delimiter", ","))[0],
"quoting": cint(form_dict.pop("csv_quoting", QUOTE_NONNUMERIC)),
"decimal_sep": cstr(form_dict.pop("csv_decimal_sep", ".")),
}
@ -44,13 +45,30 @@ def get_csv_bytes(data: list[list], csv_params: dict) -> bytes:
from csv import writer
from io import StringIO
decimal_sep = csv_params.pop("decimal_sep", None)
_data = data.copy()
if decimal_sep:
_data = apply_csv_decimal_sep(data, decimal_sep)
file = StringIO()
csv_writer = writer(file, **csv_params)
csv_writer.writerows(data)
csv_writer.writerows(_data)
return file.getvalue().encode("utf-8")
def apply_csv_decimal_sep(data: list[list], decimal_sep: str) -> list[list]:
"""Apply decimal separator to csv data."""
if decimal_sep == ".":
return data
return [
[str(value).replace(".", decimal_sep, 1) if isinstance(value, float) else value for value in row]
for row in data
]
def provide_binary_file(filename: str, extension: str, content: bytes) -> None:
"""Provide a binary file to the client."""
from frappe import _

View file

@ -1519,6 +1519,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
include_filters,
csv_delimiter,
csv_quoting,
csv_decimal_sep,
}) => {
this.make_access_log("Export", file_format);
@ -1551,6 +1552,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
visible_idx,
csv_delimiter,
csv_quoting,
csv_decimal_sep,
include_indentation,
include_filters,
};

View file

@ -203,6 +203,14 @@ frappe.report_utils = {
default: 2,
depends_on: "eval:doc.file_format=='CSV'",
},
{
fieldtype: "Data",
label: "CSV Decimal Separator",
fieldname: "csv_decimal_sep",
default: ".",
length: 1,
depends_on: "eval:doc.file_format=='CSV' && doc.csv_quoting != 2",
},
{
fieldtype: "Small Text",
label: "CSV Preview",
@ -248,7 +256,8 @@ frappe.report_utils = {
frappe.report_utils.get_csv_preview(
PREVIEW_DATA,
dialog.get_value("csv_quoting"),
dialog.get_value("csv_delimiter")
dialog.get_value("csv_delimiter"),
dialog.get_value("csv_decimal_sep")
)
);
}
@ -256,11 +265,12 @@ frappe.report_utils = {
dialog.fields_dict["file_format"].df.onchange = () => update_csv_preview(dialog);
dialog.fields_dict["csv_quoting"].df.onchange = () => update_csv_preview(dialog);
dialog.fields_dict["csv_delimiter"].df.onchange = () => update_csv_preview(dialog);
dialog.fields_dict["csv_decimal_sep"].df.onchange = () => update_csv_preview(dialog);
return dialog;
},
get_csv_preview(data, quoting, delimiter) {
get_csv_preview(data, quoting, delimiter, decimal_sep) {
// data: array of arrays
// quoting: 0 - minimal, 1 - all, 2 - non-numeric, 3 - none
// delimiter: any single character
@ -276,10 +286,18 @@ frappe.report_utils = {
frappe.throw(__("Delimiter must be a single character"));
}
if (decimal_sep.length > 1) {
frappe.throw(__("Decimal Separator must be a single character"));
}
if (0 > quoting || quoting > 3) {
frappe.throw(__("Quoting must be between 0 and 3"));
}
if (decimal_sep !== "." && quoting === QUOTING.NonNumeric) {
frappe.throw(__("Decimal Separator must be '.' when Quoting is set to Non-numeric"));
}
return data
.map((row) => {
return row
@ -292,6 +310,10 @@ frappe.report_utils = {
col = col.replace(/"/g, '""');
}
if (typeof col == "number" && decimal_sep !== ".") {
col = col.toString().replace(".", decimal_sep);
}
switch (quoting) {
case QUOTING.Minimal:
return typeof col === "string" && col.includes(delimiter)

View file

@ -1584,6 +1584,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
if (data.file_format == "CSV") {
args.csv_delimiter = data.csv_delimiter;
args.csv_quoting = data.csv_quoting;
args.csv_decimal_sep = data.csv_decimal_sep;
}
if (this.add_totals_row) {