Skip to content

Commit

Permalink
[#2903] Support import of Zaaktype configs without library
Browse files Browse the repository at this point in the history
  • Loading branch information
pi-sigma committed Dec 4, 2024
1 parent 814eb49 commit b3c97d0
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 32 deletions.
83 changes: 80 additions & 3 deletions src/open_inwoner/openzaak/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from solo.admin import SingletonModelAdmin

from open_inwoner.ckeditor5.widgets import CKEditorWidget
from open_inwoner.openzaak.import_export import CatalogusConfigImport, ZGWConfigExport
from open_inwoner.openzaak.import_export import ZGWConfigExport, ZGWConfigImport
from open_inwoner.utils.forms import LimitedUploadFileField

from .models import (
Expand Down Expand Up @@ -133,7 +133,7 @@ def get_urls(self):
path(
"import-catalogus-dump/",
self.admin_site.admin_view(self.process_file_view),
name="upload_zgw_import_file",
name="upload_catalogus_import_file",
),
]
return custom_urls + urls
Expand Down Expand Up @@ -163,7 +163,7 @@ def process_file_view(self, request):

try:
import_result = (
CatalogusConfigImport.import_from_jsonl_file_in_django_storage(
ZGWConfigImport.import_from_jsonl_file_in_django_storage(
target_file_name,
storage,
)
Expand Down Expand Up @@ -371,6 +371,7 @@ def has_delete_permission(self, request, obj=None):

@admin.register(ZaakTypeConfig)
class ZaakTypeConfigAdmin(admin.ModelAdmin):
change_list_template = "admin/zaaktypeconfig_change_list.html"
inlines = [
ZaakTypeInformatieObjectTypeConfigInline,
ZaakTypeStatusTypeConfigInline,
Expand Down Expand Up @@ -434,6 +435,17 @@ class ZaakTypeConfigAdmin(admin.ModelAdmin):
]
ordering = ("identificatie", "catalogus__domein")

def get_urls(self):
urls = super().get_urls()
custom_urls = [
path(
"import-zaaktype-dump/",
self.admin_site.admin_view(self.process_file_view),
name="upload_zaaktype_import_file",
),
]
return custom_urls + urls

@admin.action(description=_("Export to file"))
def export_zaaktype_configs(modeladmin, request, queryset):
export = ZGWConfigExport.from_zaaktype_configs(queryset)
Expand All @@ -446,6 +458,71 @@ def export_zaaktype_configs(modeladmin, request, queryset):
] = 'attachment; filename="zgw-zaaktype-export.json"'
return response

def process_file_view(self, request):
form = ImportZGWExportFileForm()

if request.method == "POST":
form = ImportZGWExportFileForm(request.POST, request.FILES)
if form.is_valid():
storage = PrivateMediaFileSystemStorage()
timestamp = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
target_file_name = f"zgw_import_dump_{timestamp}.json"
storage.save(target_file_name, request.FILES["zgw_export_file"])

try:
import_result = (
ZGWConfigImport.import_from_jsonl_file_in_django_storage(
target_file_name,
storage,
)
)
self.message_user(
request,
_(
"%(num_rows)d item(s) processed in total, with %(error_rows)d failing row(s)."
% {
"num_rows": import_result.total_rows_processed,
"error_rows": len(import_result.import_errors),
}
),
messages.SUCCESS
if not import_result.import_errors
else messages.WARNING,
)
if errors := import_result.import_errors:
msgs_deduped = set(error.__str__() for error in errors)
error_msg_iterator = ([msg] for msg in msgs_deduped)

error_msg_html = format_html_join(
"\n", "<p> - {}</p>", error_msg_iterator
)
error_msg_html = format_html(
_("It was not possible to import the following items:")
+ f"<div>{error_msg_html}</div>"
)
self.message_user(request, error_msg_html, messages.ERROR)

return HttpResponseRedirect(
reverse(
"admin:openzaak_zaaktypeconfig_changelist",
)
)
except Exception:
logger.exception("Unable to process ZGW import")
self.message_user(
request,
_(
"We were unable to process your upload. Please regenerate the file and try again."
),
messages.ERROR,
)
finally:
storage.delete(target_file_name)

return TemplateResponse(
request, "admin/import_zgw_export_form.html", {"form": form}
)

def has_add_permission(self, request):
return False

Expand Down
2 changes: 1 addition & 1 deletion src/open_inwoner/openzaak/import_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ def from_zaaktype_configs(cls, zaaktype_configs: QuerySet) -> Self:


@dataclasses.dataclass(frozen=True)
class CatalogusConfigImport:
class ZGWConfigImport:
"""Import CatalogusConfig(s) and all associated relations."""

total_rows_processed: int = 0
Expand Down
12 changes: 6 additions & 6 deletions src/open_inwoner/openzaak/tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ def test_import_flow_reports_success(self) -> None:

form = self.app.get(
reverse(
"admin:upload_zgw_import_file",
"admin:upload_catalogus_import_file",
),
user=self.user,
).form
Expand All @@ -219,13 +219,13 @@ def test_import_flow_reports_success(self) -> None:
)

@mock.patch(
"open_inwoner.openzaak.import_export.CatalogusConfigImport.import_from_jsonl_file_in_django_storage"
"open_inwoner.openzaak.import_export.ZGWConfigImport.import_from_jsonl_file_in_django_storage"
)
def test_import_flow_errors_reports_failure_to_user(self, m) -> None:
m.side_effect = Exception("something went wrong")
form = self.app.get(
reverse(
"admin:upload_zgw_import_file",
"admin:upload_catalogus_import_file",
),
user=self.user,
).form
Expand Down Expand Up @@ -255,7 +255,7 @@ def test_import_flow_errors_reports_failure_to_user(self, m) -> None:
self.assertEqual(
response.request.path,
reverse(
"admin:upload_zgw_import_file",
"admin:upload_catalogus_import_file",
),
)

Expand All @@ -273,7 +273,7 @@ def test_import_flow_reports_errors(self) -> None:

form = self.app.get(
reverse(
"admin:upload_zgw_import_file",
"admin:upload_catalogus_import_file",
),
user=self.user,
).form
Expand Down Expand Up @@ -334,7 +334,7 @@ def test_import_flow_reports_partial_errors(self) -> None:

form = self.app.get(
reverse(
"admin:upload_zgw_import_file",
"admin:upload_catalogus_import_file",
),
user=self.user,
).form
Expand Down
36 changes: 15 additions & 21 deletions src/open_inwoner/openzaak/tests/test_import_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.core.files.storage.memory import InMemoryStorage
from django.test import TestCase

from open_inwoner.openzaak.import_export import CatalogusConfigImport, ZGWConfigExport
from open_inwoner.openzaak.import_export import ZGWConfigExport, ZGWConfigImport
from open_inwoner.openzaak.models import (
CatalogusConfig,
ZaakTypeConfig,
Expand Down Expand Up @@ -348,14 +348,14 @@ def test_import_jsonl_update_success(self):
mocks = ZGWExportImportMockData()
self.storage.save("import.jsonl", io.StringIO(self.jsonl))

import_result = CatalogusConfigImport.import_from_jsonl_file_in_django_storage(
import_result = ZGWConfigImport.import_from_jsonl_file_in_django_storage(
"import.jsonl", self.storage
)

# check import
self.assertEqual(
import_result,
CatalogusConfigImport(
ZGWConfigImport(
total_rows_processed=5,
catalogus_configs_imported=1,
zaaktype_configs_imported=1,
Expand Down Expand Up @@ -435,7 +435,7 @@ def test_import_jsonl_missing_statustype_config(self):
# we use `asdict` and replace the Exceptions with string representations
# because for Exceptions raised from within dataclasses, equality ==/is identity
import_result = dataclasses.asdict(
CatalogusConfigImport.import_from_jsonl_file_in_django_storage(
ZGWConfigImport.import_from_jsonl_file_in_django_storage(
"import.jsonl", self.storage
)
)
Expand All @@ -444,7 +444,7 @@ def test_import_jsonl_missing_statustype_config(self):
"ZaakTypeConfig identificatie = 'ztc-id-a-0', Catalogus domein = 'DM-0', Catalogus rsin = '123456789'"
)
import_expected = dataclasses.asdict(
CatalogusConfigImport(
ZGWConfigImport(
total_rows_processed=6,
catalogus_configs_imported=1,
zaaktype_configs_imported=1,
Expand Down Expand Up @@ -481,7 +481,7 @@ def test_import_jsonl_update_statustype_config_missing_zt_config(self):
# we use `asdict` and replace the Exceptions with string representations
# because for Exceptions raised from within dataclasses, equality ==/is identity
import_result = dataclasses.asdict(
CatalogusConfigImport.import_from_jsonl_file_in_django_storage(
ZGWConfigImport.import_from_jsonl_file_in_django_storage(
"import.jsonl", self.storage
)
)
Expand All @@ -490,7 +490,7 @@ def test_import_jsonl_update_statustype_config_missing_zt_config(self):
"ZaakTypeConfig identificatie = 'bogus', Catalogus domein = 'DM-1', Catalogus rsin = '666666666'"
)
import_expected = dataclasses.asdict(
CatalogusConfigImport(
ZGWConfigImport(
total_rows_processed=6,
catalogus_configs_imported=1,
zaaktype_configs_imported=1,
Expand Down Expand Up @@ -527,7 +527,7 @@ def test_import_jsonl_update_reports_duplicates(self):
# we use `asdict` and replace the Exceptions with string representations
# because for Exceptions raised from within dataclasses, equality ==/is identity
import_result = dataclasses.asdict(
CatalogusConfigImport.import_from_jsonl_file_in_django_storage(
ZGWConfigImport.import_from_jsonl_file_in_django_storage(
"import.jsonl", self.storage
)
)
Expand All @@ -536,7 +536,7 @@ def test_import_jsonl_update_reports_duplicates(self):
"ZaakTypeConfig identificatie = 'ztc-id-a-0', Catalogus domein = 'DM-0', Catalogus rsin = '123456789'"
)
import_expected = dataclasses.asdict(
CatalogusConfigImport(
ZGWConfigImport(
total_rows_processed=5,
catalogus_configs_imported=1,
zaaktype_configs_imported=1,
Expand Down Expand Up @@ -569,9 +569,7 @@ def test_import_jsonl_fails_with_catalogus_domein_rsin_mismatch(self):
with self.assertLogs(
logger="open_inwoner.openzaak.import_export", level="ERROR"
) as cm:
import_result = CatalogusConfigImport.from_jsonl_stream_or_string(
import_line
)
import_result = ZGWConfigImport.from_jsonl_stream_or_string(import_line)
self.assertEqual(
cm.output,
[
Expand All @@ -598,7 +596,7 @@ def test_import_jsonl_fails_with_catalogus_domein_rsin_mismatch(self):
def test_bad_import_types(self):
for bad_type in (set(), list(), b""):
with self.assertRaises(ValueError):
CatalogusConfigImport.from_jsonl_stream_or_string(bad_type)
ZGWConfigImport.from_jsonl_stream_or_string(bad_type)

def test_valid_input_types_are_accepted(self):
ZGWExportImportMockData()
Expand All @@ -609,10 +607,10 @@ def test_valid_input_types_are_accepted(self):
self.jsonl,
):
with self.subTest(f"Input type {type(input)}"):
import_result = CatalogusConfigImport.from_jsonl_stream_or_string(input)
import_result = ZGWConfigImport.from_jsonl_stream_or_string(input)
self.assertEqual(
import_result,
CatalogusConfigImport(
ZGWConfigImport(
total_rows_processed=5,
catalogus_configs_imported=1,
zaaktype_configs_imported=1,
Expand All @@ -628,9 +626,7 @@ def test_import_is_atomic(self):
bad_jsonl = self.jsonl + "\n" + bad_line

with self.assertRaises(KeyError):
CatalogusConfigImport.from_jsonl_stream_or_string(
stream_or_string=bad_jsonl
)
ZGWConfigImport.from_jsonl_stream_or_string(stream_or_string=bad_jsonl)

counts = (
CatalogusConfig.objects.count(),
Expand All @@ -654,8 +650,6 @@ def setUp(self):

def test_exports_can_be_imported(self):
export = ZGWConfigExport.from_catalogus_configs(CatalogusConfig.objects.all())
import_result = CatalogusConfigImport.from_jsonl_stream_or_string(
export.as_jsonl()
)
import_result = ZGWConfigImport.from_jsonl_stream_or_string(export.as_jsonl())

self.assertEqual(import_result.total_rows_processed, 5)
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{% block object-tools-items %}
{{ block.super }}
<li>
<a href="{% url 'admin:upload_zgw_import_file' %}" class="addlink">
<a href="{% url 'admin:upload_catalogus_import_file' %}" class="addlink">
{% trans "Import from file" %}
</a>
</li>
Expand Down
12 changes: 12 additions & 0 deletions src/open_inwoner/templates/admin/zaaktypeconfig_change_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% extends "admin/change_list.html" %}
{% load i18n admin_urls %}

{% block object-tools-items %}
{{ block.super }}
<li>
<a href="{% url 'admin:upload_zaaktype_import_file' %}" class="addlink">
{% trans "Import from file" %}
</a>
</li>
{% endblock %}

0 comments on commit b3c97d0

Please sign in to comment.