Skip to content

Commit 07cfcab

Browse files
authored
Merge pull request #525 from maykinmedia/feature/502-make-report-xlsx
[#502] Make destruction report xlsx file instead of csv
2 parents e2471d2 + 4c2b867 commit 07cfcab

13 files changed

+211
-64
lines changed

backend/requirements/base.in

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,5 @@ celery
3737
# Additional libraries
3838
zgw-consumers
3939
furl
40-
python-slugify
40+
python-slugify
41+
XlsxWriter

backend/requirements/base.txt

+2
Original file line numberDiff line numberDiff line change
@@ -261,5 +261,7 @@ webauthn==2.1.0
261261
# via django-two-factor-auth
262262
wrapt==1.14.1
263263
# via elastic-apm
264+
xlsxwriter==3.2.0
265+
# via -r requirements/base.in
264266
zgw-consumers==0.36.1
265267
# via -r requirements/base.in

backend/requirements/ci.txt

+8
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ elastic-apm==6.22.0
280280
# via
281281
# -c requirements/base.txt
282282
# -r requirements/base.txt
283+
et-xmlfile==2.0.0
284+
# via openpyxl
283285
face==20.1.1
284286
# via
285287
# -c requirements/base.txt
@@ -369,6 +371,8 @@ multidict==6.0.5
369371
# via yarl
370372
mypy-extensions==1.0.0
371373
# via black
374+
openpyxl==3.1.5
375+
# via -r requirements/test-tools.in
372376
orderedmultidict==1.0.1
373377
# via
374378
# -c requirements/base.txt
@@ -630,6 +634,10 @@ wrapt==1.14.1
630634
# -r requirements/base.txt
631635
# elastic-apm
632636
# vcrpy
637+
xlsxwriter==3.2.0
638+
# via
639+
# -c requirements/base.txt
640+
# -r requirements/base.txt
633641
yarl==1.9.4
634642
# via vcrpy
635643
zgw-consumers==0.36.1

backend/requirements/dev.txt

+13
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,11 @@ elastic-apm==6.22.0
324324
# via
325325
# -c requirements/ci.txt
326326
# -r requirements/ci.txt
327+
et-xmlfile==2.0.0
328+
# via
329+
# -c requirements/ci.txt
330+
# -r requirements/ci.txt
331+
# openpyxl
327332
face==20.1.1
328333
# via
329334
# -c requirements/ci.txt
@@ -456,6 +461,10 @@ mypy-extensions==1.0.0
456461
# -c requirements/ci.txt
457462
# -r requirements/ci.txt
458463
# black
464+
openpyxl==3.1.5
465+
# via
466+
# -c requirements/ci.txt
467+
# -r requirements/ci.txt
459468
orderedmultidict==1.0.1
460469
# via
461470
# -c requirements/ci.txt
@@ -820,6 +829,10 @@ wrapt==1.14.1
820829
# -r requirements/ci.txt
821830
# elastic-apm
822831
# vcrpy
832+
xlsxwriter==3.2.0
833+
# via
834+
# -c requirements/ci.txt
835+
# -r requirements/ci.txt
823836
yarl==1.9.4
824837
# via
825838
# -c requirements/ci.txt

backend/requirements/test-tools.in

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ testfixtures
1717
vcrpy
1818
pytest-playwright
1919
docker
20+
# XlsxWriter is more suitable for writing large files, but doesn't support reading them.
21+
# So for the tests we use openpyxl to check the created excel files.
22+
openpyxl
2023

2124
# Documentation
2225
sphinx
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from dataclasses import dataclass
2+
from typing import IO
3+
4+
from django.utils.translation import gettext
5+
6+
import xlsxwriter
7+
from glom import glom
8+
from xlsxwriter.worksheet import Worksheet
9+
10+
from openarchiefbeheer.zaken.api.constants import ZAAK_METADATA_FIELDS_MAPPINGS
11+
12+
from .constants import InternalStatus
13+
from .models import DestructionList
14+
15+
16+
@dataclass
17+
class DestructionReportGenerator:
18+
destruction_list: DestructionList
19+
20+
def add_zaken_table(self, worksheet: Worksheet, start_row: int = 0) -> None:
21+
worksheet.write_row(
22+
start_row, 0, [field["name"] for field in ZAAK_METADATA_FIELDS_MAPPINGS]
23+
)
24+
25+
for row_count, item in enumerate(
26+
self.destruction_list.items.filter(
27+
processing_status=InternalStatus.succeeded
28+
).iterator(chunk_size=1000)
29+
):
30+
data = [
31+
glom(item.extra_zaak_data, field["path"], default="")
32+
for field in ZAAK_METADATA_FIELDS_MAPPINGS
33+
]
34+
worksheet.write_row(start_row + row_count + 1, 0, data)
35+
36+
def generate_destruction_report(self, file: IO) -> None:
37+
workbook = xlsxwriter.Workbook(file.name, options={"in_memory": False})
38+
39+
worksheet = workbook.add_worksheet(name=gettext("Deleted zaken"))
40+
41+
self.add_zaken_table(worksheet)
42+
43+
workbook.close()

backend/src/openarchiefbeheer/destruction/models.py

+8-36
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import csv
21
import logging
32
import traceback
43
import uuid as _uuid
@@ -220,48 +219,21 @@ def abort_destruction(self) -> None:
220219
self.save()
221220

222221
def generate_destruction_report(self) -> None:
222+
from .destruction_report import DestructionReportGenerator
223+
223224
if not self.status == ListStatus.deleted:
224225
logger.warning("The destruction list has not been deleted yet.")
225226
return
226227

227-
fieldnames = [
228-
"url",
229-
"einddatum",
230-
"resultaat",
231-
"startdatum",
232-
"omschrijving",
233-
"identificatie",
234-
"zaaktype url",
235-
"zaaktype omschrijving",
236-
"selectielijst procestype nummer",
237-
]
238-
with NamedTemporaryFile(mode="w", newline="", delete_on_close=False) as f_tmp:
239-
writer = csv.DictWriter(f_tmp, fieldnames=fieldnames)
240-
writer.writeheader()
241-
for item in self.items.filter(
242-
processing_status=InternalStatus.succeeded
243-
).iterator(chunk_size=1000):
244-
data = {
245-
**item.extra_zaak_data,
246-
**{
247-
"zaaktype url": item.extra_zaak_data["zaaktype"]["url"],
248-
"zaaktype omschrijving": item.extra_zaak_data["zaaktype"][
249-
"omschrijving"
250-
],
251-
"selectielijst procestype nummer": item.extra_zaak_data[
252-
"zaaktype"
253-
]["selectielijst_procestype"]["nummer"],
254-
},
255-
}
256-
del data["zaaktype"]
257-
258-
writer.writerow(data)
259-
228+
generator = DestructionReportGenerator(destruction_list=self)
229+
with NamedTemporaryFile(mode="wb", delete_on_close=False) as f_tmp:
230+
generator.generate_destruction_report(f_tmp)
260231
f_tmp.close()
261-
with open(f_tmp.name, mode="r") as f:
232+
233+
with open(f_tmp.name, mode="rb") as f:
262234
django_file = File(f)
263235
self.destruction_report.save(
264-
f"report_{slugify(self.name)}.csv", django_file
236+
f"report_{slugify(self.name)}.xlsx", django_file
265237
)
266238

267239
self.save()

backend/src/openarchiefbeheer/destruction/tests/test_models.py

+54-11
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
from django.core.exceptions import ObjectDoesNotExist
55
from django.test import TestCase
66
from django.utils import timezone
7+
from django.utils.translation import gettext
78

89
from freezegun import freeze_time
10+
from openpyxl import load_workbook
911
from privates.test import temp_private_root
1012
from requests import HTTPError
1113
from requests_mock import Mocker
@@ -344,25 +346,66 @@ def test_generate_destruction_report(self):
344346

345347
destruction_list.refresh_from_db()
346348

347-
destruction_list.destruction_report
348-
lines = [line for line in destruction_list.destruction_report.readlines()]
349+
wb = load_workbook(filename=destruction_list.destruction_report.path)
350+
sheet_deleted_zaken = wb[gettext("Deleted zaken")]
351+
rows = list(sheet_deleted_zaken.iter_rows(values_only=True))
349352

350-
self.assertEqual(len(lines), 4)
353+
self.assertEqual(len(rows), 4)
351354
self.assertEqual(
352-
lines[0],
353-
b"url,einddatum,resultaat,startdatum,omschrijving,identificatie,zaaktype url,zaaktype omschrijving,selectielijst procestype nummer\n",
355+
rows[0],
356+
(
357+
"url",
358+
"einddatum",
359+
"resultaat",
360+
"startdatum",
361+
"omschrijving",
362+
"identificatie",
363+
"zaaktype url",
364+
"zaaktype omschrijving",
365+
"selectielijst procestype nummer",
366+
),
354367
)
355368
self.assertEqual(
356-
lines[1],
357-
b"http://zaken.nl/api/v1/zaken/111-111-111,2022-01-01,http://zaken.nl/api/v1/resultaten/111-111-111,2020-01-01,Test description 1,ZAAK-01,http://catalogi.nl/api/v1/zaaktypen/111-111-111,Tralala zaaktype,1\n",
369+
rows[1],
370+
(
371+
"http://zaken.nl/api/v1/zaken/111-111-111",
372+
"2022-01-01",
373+
"http://zaken.nl/api/v1/resultaten/111-111-111",
374+
"2020-01-01",
375+
"Test description 1",
376+
"ZAAK-01",
377+
"http://catalogi.nl/api/v1/zaaktypen/111-111-111",
378+
"Tralala zaaktype",
379+
1,
380+
),
358381
)
359382
self.assertEqual(
360-
lines[2],
361-
b"http://zaken.nl/api/v1/zaken/111-111-222,2022-01-02,http://zaken.nl/api/v1/resultaten/111-111-222,2020-01-02,Test description 2,ZAAK-02,http://catalogi.nl/api/v1/zaaktypen/111-111-111,Tralala zaaktype,1\n",
383+
rows[2],
384+
(
385+
"http://zaken.nl/api/v1/zaken/111-111-222",
386+
"2022-01-02",
387+
"http://zaken.nl/api/v1/resultaten/111-111-222",
388+
"2020-01-02",
389+
"Test description 2",
390+
"ZAAK-02",
391+
"http://catalogi.nl/api/v1/zaaktypen/111-111-111",
392+
"Tralala zaaktype",
393+
1,
394+
),
362395
)
363396
self.assertEqual(
364-
lines[3],
365-
b"http://zaken.nl/api/v1/zaken/111-111-333,2022-01-03,http://zaken.nl/api/v1/resultaten/111-111-333,2020-01-03,Test description 3,ZAAK-03,http://catalogi.nl/api/v1/zaaktypen/111-111-222,Tralala zaaktype,2\n",
397+
rows[3],
398+
(
399+
"http://zaken.nl/api/v1/zaken/111-111-333",
400+
"2022-01-03",
401+
"http://zaken.nl/api/v1/resultaten/111-111-333",
402+
"2020-01-03",
403+
"Test description 3",
404+
"ZAAK-03",
405+
"http://catalogi.nl/api/v1/zaaktypen/111-111-222",
406+
"Tralala zaaktype",
407+
2,
408+
),
366409
)
367410

368411
def test_zaak_creation_skipped_if_internal_status_succeeded(self):

backend/src/openarchiefbeheer/destruction/tests/test_tasks.py

+58-13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from django.utils.translation import gettext as _, ngettext
88

99
from freezegun import freeze_time
10+
from openpyxl import load_workbook
1011
from privates.test import temp_private_root
1112
from requests import HTTPError
1213
from requests_mock import Mocker
@@ -439,16 +440,38 @@ def test_process_list(self):
439440
).exists()
440441
)
441442

442-
lines = [line for line in destruction_list.destruction_report.readlines()]
443+
wb = load_workbook(filename=destruction_list.destruction_report.path)
444+
sheet_deleted_zaken = wb[_("Deleted zaken")]
445+
rows = list(sheet_deleted_zaken.iter_rows(values_only=True))
443446

444-
self.assertEqual(len(lines), 3)
447+
self.assertEqual(len(rows), 3)
445448
self.assertEqual(
446-
lines[1],
447-
b"http://zaken.nl/api/v1/zaken/111-111-111,2022-01-01,http://zaken.nl/api/v1/resultaten/111-111-111,2020-01-01,Test description 1,ZAAK-01,http://catalogue-api.nl/zaaktypen/111-111-111,Aangifte behandelen,1\n",
449+
rows[1],
450+
(
451+
"http://zaken.nl/api/v1/zaken/111-111-111",
452+
"2022-01-01",
453+
"http://zaken.nl/api/v1/resultaten/111-111-111",
454+
"2020-01-01",
455+
"Test description 1",
456+
"ZAAK-01",
457+
"http://catalogue-api.nl/zaaktypen/111-111-111",
458+
"Aangifte behandelen",
459+
1,
460+
),
448461
)
449462
self.assertEqual(
450-
lines[2],
451-
b"http://zaken.nl/api/v1/zaken/222-222-222,2022-01-02,http://zaken.nl/api/v1/resultaten/111-111-222,2020-01-02,Test description 2,ZAAK-02,http://catalogue-api.nl/zaaktypen/111-111-111,Aangifte behandelen,1\n",
463+
rows[2],
464+
(
465+
"http://zaken.nl/api/v1/zaken/222-222-222",
466+
"2022-01-02",
467+
"http://zaken.nl/api/v1/resultaten/111-111-222",
468+
"2020-01-02",
469+
"Test description 2",
470+
"ZAAK-02",
471+
"http://catalogue-api.nl/zaaktypen/111-111-111",
472+
"Aangifte behandelen",
473+
1,
474+
),
452475
)
453476

454477
m_zaak.assert_called()
@@ -583,19 +606,41 @@ def test_complete_and_notify(self):
583606
self.assertEqual(destruction_list.processing_status, InternalStatus.succeeded)
584607
self.assertEqual(
585608
destruction_list.destruction_report.name,
586-
"destruction_reports/2024/10/09/report_some-destruction-list.csv",
609+
"destruction_reports/2024/10/09/report_some-destruction-list.xlsx",
587610
)
588611

589-
lines = [line for line in destruction_list.destruction_report.readlines()]
612+
wb = load_workbook(filename=destruction_list.destruction_report.path)
613+
sheet_deleted_zaken = wb[_("Deleted zaken")]
614+
rows = list(sheet_deleted_zaken.iter_rows(values_only=True))
590615

591-
self.assertEqual(len(lines), 2)
616+
self.assertEqual(len(rows), 2)
592617
self.assertEqual(
593-
lines[0],
594-
b"url,einddatum,resultaat,startdatum,omschrijving,identificatie,zaaktype url,zaaktype omschrijving,selectielijst procestype nummer\n",
618+
rows[0],
619+
(
620+
"url",
621+
"einddatum",
622+
"resultaat",
623+
"startdatum",
624+
"omschrijving",
625+
"identificatie",
626+
"zaaktype url",
627+
"zaaktype omschrijving",
628+
"selectielijst procestype nummer",
629+
),
595630
)
596631
self.assertEqual(
597-
lines[1],
598-
b"http://zaken.nl/api/v1/zaken/111-111-111,2022-01-01,http://zaken.nl/api/v1/resultaten/111-111-111,2020-01-01,Test description 1,ZAAK-01,http://catalogi.nl/api/v1/zaaktypen/111-111-111,Tralala zaaktype,1\n",
632+
rows[1],
633+
(
634+
"http://zaken.nl/api/v1/zaken/111-111-111",
635+
"2022-01-01",
636+
"http://zaken.nl/api/v1/resultaten/111-111-111",
637+
"2020-01-01",
638+
"Test description 1",
639+
"ZAAK-01",
640+
"http://catalogi.nl/api/v1/zaaktypen/111-111-111",
641+
"Tralala zaaktype",
642+
1,
643+
),
599644
)
600645

601646
@override_settings(CELERY_TASK_ALWAYS_EAGER=True)

0 commit comments

Comments
 (0)