From 1c00f21eebd915bc22ea370b1ab1e34fd50175a6 Mon Sep 17 00:00:00 2001 From: Michel Succar Medina <53895656+micsucmed@users.noreply.github.com> Date: Fri, 8 Dec 2023 12:16:27 +0100 Subject: [PATCH] Use CSV writer for answer exports (#456) --- newdle/export.py | 20 +++++++++++++++++--- tests/api_test.py | 4 ++-- tests/export/answers.csv | 4 ++-- tests/export/answers.xlsx | Bin 5468 -> 5476 bytes 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/newdle/export.py b/newdle/export.py index 2b50d7a0..1a7b0ca4 100644 --- a/newdle/export.py +++ b/newdle/export.py @@ -1,5 +1,7 @@ +import csv import datetime -from io import BytesIO +from contextlib import contextmanager +from io import BytesIO, TextIOWrapper from xlsxwriter import Workbook @@ -25,12 +27,24 @@ def _generate_answers_for_export(newdle): return rows +@contextmanager +def csv_text_io_wrapper(buf): + """IO wrapper to use the csv reader/writer on a byte stream.""" + w = TextIOWrapper(buf, encoding='utf-8-sig', newline='') + try: + yield w + finally: + w.detach() + + def export_answers_to_csv(newdle): rows = _generate_answers_for_export(newdle) - csv = '\n'.join([','.join(row) for row in rows]) buffer = BytesIO() - buffer.write(csv.encode('utf-8-sig')) + with csv_text_io_wrapper(buffer) as csvbuf: + writer = csv.writer(csvbuf, dialect='unix', quoting=csv.QUOTE_MINIMAL) + writer.writerows(rows) buffer.seek(0) + return buffer diff --git a/tests/api_test.py b/tests/api_test.py index bb632243..b4ed989b 100644 --- a/tests/api_test.py +++ b/tests/api_test.py @@ -1374,13 +1374,13 @@ def test_answer_export(snapshot, monkeypatch, flask_client, dummy_uid): snapshot.snapshot_dir = Path(__file__).parent / 'export' p1 = Participant.query.filter_by(code='part1').first() p1.answers = {datetime(2019, 9, 11, 14, 0): Availability.available} - p1.comment = 'Available comment' + p1.comment = 'Hello, world' Participant.query.filter_by(code='part2').first().answers = { datetime(2019, 9, 11, 14, 0): Availability.unavailable } p3 = Participant.query.filter_by(code='part3').first() p3.answers = {datetime(2019, 9, 11, 14, 0): Availability.ifneedbe} - p3.comment = 'Comment' + p3.comment = 'Hello world' resp = flask_client.get( url_for('api.export_participants', code='dummy', format='csv'), diff --git a/tests/export/answers.csv b/tests/export/answers.csv index 94aedb57..e8d02088 100644 --- a/tests/export/answers.csv +++ b/tests/export/answers.csv @@ -1,4 +1,4 @@ Participant name,2019-09-11T13:00,2019-09-11T14:00,2019-09-12T13:00,2019-09-12T13:30,Comment Albert Einstein,,unavailable,,, -Guinea Pig,,ifneedbe,,,Comment -Tony Stark,,available,,,Available comment \ No newline at end of file +Guinea Pig,,ifneedbe,,,Hello world +Tony Stark,,available,,,"Hello, world" diff --git a/tests/export/answers.xlsx b/tests/export/answers.xlsx index f7a43dcff6b02df4f5b521ca20c06c9f08caf50c..a9789008f1c6cc6cd10c129a6196865538e387f2 100644 GIT binary patch delta 750 zcmV<K0ulY(D#zyk@NmthHy0RRB_lgR@te@Qo>N>UX{1P3I<c6XCWVv%?v+qCfa z*eR{D9k_Y?mYL@zSbtX~*@MyoWk~&rM+wLTQz7$^zQ4tn*OX|(Wy(vG5YivebRAs; z2UMTB05HTFNFCC`n0m#SP70{Fo?s2qA{nZR8(XTJ=~@9#8&OrVspl<O#f79thn4z| ze+OonNMMV}R|V1>O%#;e*zt8CYE7dc6&7=Oh=MGn8-EqgDT{)p^M`<gZmo&oyH_Y7 zF_7B)l(=9!#7_r4+AYsX*+3gNLp>{!LdL&J^MZ#55P4y&$>Q8?%MFhLg$JS<b6s<H zR{m;f=S<w@#_cwkRl4c-kr%MNZJGQre{P2izstBAGNxU|cF34@8TUiRyvvA(jK!J3 zY{qlY>1fbtOStR>Xk%C7rn6DgSxb1`Gi_rxy)13a_r^QUM?22j%Kk-kz}_9i9dOp` z$y)EF<~clZl?$m!37HjnlPgNp@p(0CgLSi7P_jc~usT(R{bHb8Wg}*Y=2W;>1$_Rr z(J!-i1%m+zYUb`a1OWg5E|UWXD1Uj76wowKTQs449FfKzz_g}VOPnro*?9YPOxmGn zaI}B=+p?T5zr0fijEMplIvZQ9kgHG=H-&!NJdN(PN&;Mi3yg(6V$#d<CQnIJALUf& zy-2$-CRKa%kj9}!?qwIE2kFb$nAApuI_(j~J2SEN&Uhf!stO%Tzjmfo$A9$E;p(qZ z=84K&$~8oxidtY%3?6eMWo~}#*C#fckL-MuWt(ieu=bMAF8Sn2{^Zj$Uj^@xW!Qdj zTa2O}iIX7lkUNG0kb|u|JLtJ1Mo?>NhC(|=thZ;07j!PD&k$Wbq)p(XdKHN8L-}|A zY1-{@oarB4egjZT0|XQR0J9+ocMA!hmthHy0RRB_lc5uO32Nr<IRpU!04|dx6jK2r glXw(50r!)$6hHyklk^lm0V$I+6(a`D5&!@I0EL=fEdT%j delta 771 zcmV+e1N{8tD%>itzyk@+K3|fL0RRB^lgR@te`&f>C8>ZC!2t=e-Q8r8SR|gvHZA-; zc1o)(mEh*_TV|e@VEt8<WCuzMlp!6DJW4<&m<pMP^zAjiTvDP9mnkn%LP)<s({*$a z>`{H_0>BV!AazI!W9k)SIw_#ydW1Ddi)5%OZfvP?rfUT}ZA4Yce9xP+iVH~(4lDH^ ze-F$uk-!F%&kCeDm?$W@vE%DP)S5;?DlF#m5CvICug9x+N?8;%o!<rQb!$xw-@ZTz ziGkGSr^E%@B7QjV!ESj@$_CoF8R|)q6f*u<nrGbKgUAbGO=hQV8*X?MDBKg(nCqIm zvl_4FcFx3YUc21}vr0GpF7g7lvn`Wff5y#_G43*MhYY{V*bEtyF5_;<n06WQkTE+k zn9X<!>Yokjw}kUvfHt-`Z#p?^I%x@)J<~RJ)yp#ZJH_;D!)f~(dN+030ehDecR;__ zleL~p&2xC<Di>0d5;80DMhi;R;dM1@gLSi-QL;s2usT+S{b8V7WfNwI=2*B#34Hpq z(GO5d0|b-Y6B@IX1!Vyao}H#x`~Uy|7y<wQ6abSy6c&>n2MvE6k^-6rYKsusc12qE z0B&nqi^k~^7mc@H_jM^6JK8_}Wm`_S@2*p443Ru%I+<IokSkvkH<^Ado@NhPMFFm% z^Ng9kVAR|EE{##t80DDhR;1q;6RQ>-#JTU0M>+c7K*lmOCiVfLjxD0N&Md6GHx7uk zs{Ft*j$LUrFui{bxc_M^b)H7bQ^^kygevNRMKL%`jpV8Mv~OS7WIeO%S&|gVa%1f+ zU)}P>Klv|TUiscThb+_ft}8K!dL)j5#8Vy^&OjZM-PJ+Q12KX+P%{-eGGbj`B?`|M z^&${XQ~7hq?ia=vO!hb9Yx56KO9KQH000080000X05Fqg6c4jN2w)2d&OTp~j{yJx z_mim;dI_GLrda#{000=1EEH1#8k2w&IsxyKyA(hH(3AWWJ^>+<I~5}a#u5Mk008O+ BP_O_1