diff --git a/src/open_inwoner/openzaak/admin.py b/src/open_inwoner/openzaak/admin.py index c26478311..7695a05cf 100644 --- a/src/open_inwoner/openzaak/admin.py +++ b/src/open_inwoner/openzaak/admin.py @@ -13,7 +13,6 @@ from django.utils.html import format_html, format_html_join from django.utils.translation import gettext_lazy as _, ngettext -from import_export.admin import ImportExportMixin from privates.storages import PrivateMediaFileSystemStorage from solo.admin import SingletonModelAdmin @@ -21,6 +20,7 @@ from open_inwoner.openzaak.import_export import ( CatalogusConfigExport, CatalogusConfigImport, + ZaakTypeConfigExport, ) from open_inwoner.utils.forms import LimitedUploadFileField @@ -374,7 +374,7 @@ def has_delete_permission(self, request, obj=None): @admin.register(ZaakTypeConfig) -class ZaakTypeConfigAdmin(ImportExportMixin, admin.ModelAdmin): +class ZaakTypeConfigAdmin(admin.ModelAdmin): inlines = [ ZaakTypeInformatieObjectTypeConfigInline, ZaakTypeStatusTypeConfigInline, @@ -383,6 +383,7 @@ class ZaakTypeConfigAdmin(ImportExportMixin, admin.ModelAdmin): actions = [ "mark_as_notify_status_changes", "mark_as_not_notify_status_changes", + "export_zaaktype_configs", ] fields = [ "urls", @@ -437,6 +438,18 @@ class ZaakTypeConfigAdmin(ImportExportMixin, admin.ModelAdmin): ] ordering = ("identificatie", "catalogus__domein") + @admin.action(description=_("Export to file")) + def export_zaaktype_configs(modeladmin, request, queryset): + export = ZaakTypeConfigExport.from_zaaktype_configs(queryset) + response = StreamingHttpResponse( + export.as_jsonl_iter(), + content_type="application/json", + ) + response[ + "Content-Disposition" + ] = 'attachment; filename="zgw-zaaktype-export.json"' + return response + def has_add_permission(self, request): return False diff --git a/src/open_inwoner/openzaak/import_export.py b/src/open_inwoner/openzaak/import_export.py index 519bf36f2..1258a4d09 100644 --- a/src/open_inwoner/openzaak/import_export.py +++ b/src/open_inwoner/openzaak/import_export.py @@ -152,7 +152,65 @@ def _update_nested_zgw_config( @dataclasses.dataclass(frozen=True) -class CatalogusConfigExport: +class ZGWConfigExport: + def as_dicts_iter(self) -> Generator[dict, Any, None]: + for qs in self: + serialized_data = serializers.serialize( + queryset=qs, + format="json", + use_natural_foreign_keys=True, + use_natural_primary_keys=True, + ) + json_data: list[dict] = json.loads( + serialized_data, + ) + yield from json_data + + def as_jsonl_iter(self) -> Generator[str, Any, None]: + for row in self.as_dicts(): + yield json.dumps(row) + yield "\n" + + def as_dicts(self) -> list[dict]: + return list(self.as_dicts_iter()) + + def as_jsonl(self) -> str: + return "".join(self.as_jsonl_iter()) + + +@dataclasses.dataclass(frozen=True) +class ZaakTypeConfigExport(ZGWConfigExport): + """Gather and export ZaakTypeConfig(s).""" + + zaaktype_configs: QuerySet + + def __iter__(self) -> Generator[QuerySet, Any, None]: + yield from (self.zaaktype_configs,) + + def __eq__(self, other: QuerySet) -> bool: + for a, b in zip(self, other): + if a.difference(b).exists(): + return False + + return True + + @classmethod + def from_zaaktype_configs(cls, zaaktype_configs: QuerySet) -> Self: + if not isinstance(zaaktype_configs, QuerySet): + raise TypeError( + f"`zaaktype_configs` is not a QuerySet, but a {type(zaaktype_configs)}" + ) + + if zaaktype_configs.model != ZaakTypeConfig: + raise ValueError( + f"`zaaktype_configs` is of type {zaaktype_configs.model}, not ZaakTypeConfig" + ) + + return cls(zaaktype_configs=zaaktype_configs) + + +@dataclasses.dataclass(frozen=True) +class CatalogusConfigExport(ZGWConfigExport): """Gather and export CatalogusConfig(s) and all associated relations.""" catalogus_configs: QuerySet @@ -210,30 +268,6 @@ def from_catalogus_configs(cls, catalogus_configs: QuerySet) -> Self: zaak_resultaat_type_configs=zaak_resultaat_type_configs, ) - def as_dicts_iter(self) -> Generator[dict, Any, None]: - for qs in self: - serialized_data = serializers.serialize( - queryset=qs, - format="json", - use_natural_foreign_keys=True, - use_natural_primary_keys=True, - ) - json_data: list[dict] = json.loads( - serialized_data, - ) - yield from json_data - - def as_jsonl_iter(self) -> Generator[str, Any, None]: - for row in self.as_dicts(): - yield json.dumps(row) - yield "\n" - - def as_dicts(self) -> list[dict]: - return list(self.as_dicts_iter()) - - def as_jsonl(self) -> str: - return "".join(self.as_jsonl_iter()) - @dataclasses.dataclass(frozen=True) class CatalogusConfigImport: diff --git a/src/open_inwoner/openzaak/tests/test_import_export.py b/src/open_inwoner/openzaak/tests/test_import_export.py index 50478d3e6..ebb80f552 100644 --- a/src/open_inwoner/openzaak/tests/test_import_export.py +++ b/src/open_inwoner/openzaak/tests/test_import_export.py @@ -8,6 +8,7 @@ from open_inwoner.openzaak.import_export import ( CatalogusConfigExport, CatalogusConfigImport, + ZaakTypeConfigExport, ) from open_inwoner.openzaak.models import ( CatalogusConfig, @@ -148,6 +149,37 @@ def test_only_models_related_to_exported_catalogus_config_are_included(self): ) +class ZaakTypeConfigExportTest(TestCase): + def setUp(self): + self.mocks = ZGWExportImportMockData(0) + + def test_export_zaaktype_configs(self): + export = ZaakTypeConfigExport.from_zaaktype_configs( + ZaakTypeConfig.objects.filter(pk=self.mocks.ztc.pk) + ) + rows = export.as_dicts() + + expected = [ + { + "model": "openzaak.zaaktypeconfig", + "fields": { + "urls": '["https://foo.0.maykinmedia.nl"]', + "catalogus": ["DM-0", "123456789"], + "identificatie": "ztc-id-a-0", + "omschrijving": "zaaktypeconfig", + "notify_status_changes": False, + "description": "", + "external_document_upload_url": "", + "document_upload_enabled": False, + "contact_form_enabled": False, + "contact_subject_code": "", + "relevante_zaakperiode": None, + }, + } + ] + self.assertEqual(rows, expected) + + class TestCatalogusExport(TestCase): def setUp(self): self.mocks = [