Merge pull request #26163 from barredterra/csv-decimal-sep
feat(CSV export): allow custom decimal separator
This commit is contained in:
commit
e455b7b452
4 changed files with 46 additions and 3 deletions
|
|
@ -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 _
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue