forked from getredash/redash
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CSV: correctly serialize booleans and dates. (getredash#3841)
* CSV: correctly serialize booleans and dates. Closes getredash#3736, closes getredash#2751. * pep8 fixes * Move column iteration to a helper function. * Use elif, as types are mutually exclusive. * Refactor parsing implementation. * Move the csv generation fucntion
- Loading branch information
1 parent
ffe6857
commit fbaa3c2
Showing
7 changed files
with
161 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import cStringIO | ||
import csv | ||
import xlsxwriter | ||
from dateutil.parser import parse as parse_date | ||
from redash.utils import json_loads, UnicodeWriter | ||
from redash.query_runner import (TYPE_BOOLEAN, TYPE_DATE, TYPE_DATETIME) | ||
from redash.authentication.org_resolving import current_org | ||
|
||
|
||
def _convert_format(fmt): | ||
return fmt.replace('DD', '%d').replace('MM', '%m').replace('YYYY', '%Y').replace('YY', '%y').replace('HH', '%H').replace('mm', '%M').replace('ss', '%s') | ||
|
||
|
||
def _convert_bool(value): | ||
if value is True: | ||
return "true" | ||
elif value is False: | ||
return "false" | ||
|
||
return value | ||
|
||
def _convert_date(value): | ||
if not value: | ||
return value | ||
|
||
parsed = parse_date(value) | ||
|
||
return parsed.strftime(_convert_format(current_org.get_setting('date_format'))) | ||
|
||
|
||
def _convert_datetime(value): | ||
if not value: | ||
return value | ||
|
||
parsed = parse_date(value) | ||
|
||
fmt = _convert_format('{} {}'.format(current_org.get_setting('date_format'), current_org.get_setting('time_format'))) | ||
return parsed.strftime(fmt) | ||
|
||
|
||
SPECIAL_TYPES = { | ||
TYPE_BOOLEAN: _convert_bool, | ||
TYPE_DATE: _convert_date, | ||
TYPE_DATETIME: _convert_datetime | ||
} | ||
|
||
|
||
def _get_column_lists(columns): | ||
fieldnames = [] | ||
special_columns = dict() | ||
|
||
for col in columns: | ||
fieldnames.append(col['name']) | ||
|
||
for col_type in SPECIAL_TYPES.keys(): | ||
if col['type'] == col_type: | ||
special_columns[col['name']] = SPECIAL_TYPES[col_type] | ||
|
||
return fieldnames, special_columns | ||
|
||
|
||
def serialize_query_result_to_csv(query_result): | ||
s = cStringIO.StringIO() | ||
|
||
query_data = json_loads(query_result.data) | ||
|
||
fieldnames, special_columns = _get_column_lists(query_data['columns']) | ||
|
||
writer = csv.DictWriter(s, extrasaction="ignore", fieldnames=fieldnames) | ||
writer.writer = UnicodeWriter(s) | ||
writer.writeheader() | ||
|
||
for row in query_data['rows']: | ||
for col_name, converter in special_columns.iteritems(): | ||
if col_name in row: | ||
row[col_name] = converter(row[col_name]) | ||
|
||
writer.writerow(row) | ||
|
||
return s.getvalue() | ||
|
||
|
||
def serialize_query_result_to_xlsx(query_result): | ||
s = cStringIO.StringIO() | ||
|
||
query_data = json_loads(query_result.data) | ||
book = xlsxwriter.Workbook(s, {'constant_memory': True}) | ||
sheet = book.add_worksheet("result") | ||
|
||
column_names = [] | ||
for (c, col) in enumerate(query_data['columns']): | ||
sheet.write(0, c, col['name']) | ||
column_names.append(col['name']) | ||
|
||
for (r, row) in enumerate(query_data['rows']): | ||
for (c, name) in enumerate(column_names): | ||
v = row.get(name) | ||
if isinstance(v, list) or isinstance(v, dict): | ||
v = str(v).encode('utf-8') | ||
sheet.write(r + 1, c, v) | ||
|
||
book.close() | ||
|
||
return s.getvalue() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import datetime | ||
import csv | ||
import cStringIO | ||
|
||
from tests import BaseTestCase | ||
|
||
from redash import models | ||
from redash.utils import utcnow, json_dumps | ||
from redash.serializers import serialize_query_result_to_csv | ||
|
||
|
||
data = { | ||
"rows": [ | ||
{"datetime": "2019-05-26T12:39:23.026Z", "bool": True, "date": "2019-05-26"}, | ||
{"datetime": "", "bool": False, "date": ""}, | ||
{"datetime": None, "bool": None, "date": None}, | ||
], | ||
"columns": [ | ||
{"friendly_name": "bool", "type": "boolean", "name": "bool"}, | ||
{"friendly_name": "date", "type": "datetime", "name": "datetime"}, | ||
{"friendly_name": "date", "type": "date", "name": "date"} | ||
] | ||
} | ||
|
||
class CsvSerializationTest(BaseTestCase): | ||
def get_csv_content(self): | ||
query_result = self.factory.create_query_result(data=json_dumps(data)) | ||
return serialize_query_result_to_csv(query_result) | ||
|
||
def test_serializes_booleans_correctly(self): | ||
with self.app.test_request_context('/'): | ||
parsed = csv.DictReader(cStringIO.StringIO(self.get_csv_content())) | ||
rows = list(parsed) | ||
|
||
self.assertEqual(rows[0]['bool'], 'true') | ||
self.assertEqual(rows[1]['bool'], 'false') | ||
self.assertEqual(rows[2]['bool'], '') | ||
|
||
def test_serializes_datatime_with_correct_format(self): | ||
with self.app.test_request_context('/'): | ||
parsed = csv.DictReader(cStringIO.StringIO(self.get_csv_content())) | ||
rows = list(parsed) | ||
|
||
self.assertEqual(rows[0]['datetime'], '26/05/19 12:39') | ||
self.assertEqual(rows[1]['datetime'], '') | ||
self.assertEqual(rows[2]['datetime'], '') | ||
self.assertEqual(rows[0]['date'], '26/05/19') | ||
self.assertEqual(rows[1]['date'], '') | ||
self.assertEqual(rows[2]['date'], '') |