diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 8a98e6b6..cd3dcd4e 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -1,4 +1,3 @@ -version: "3.4" services: document-merge-service: image: ghcr.io/adfinis/document-merge-service:dev diff --git a/docker-compose.yml b/docker-compose.yml index 0e5a83ff..6afae37b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: "3.4" services: document-merge-service: image: ghcr.io/adfinis/document-merge-service:latest diff --git a/document_merge_service/api/serializers.py b/document_merge_service/api/serializers.py index f3867335..bddcc65c 100644 --- a/document_merge_service/api/serializers.py +++ b/document_merge_service/api/serializers.py @@ -1,3 +1,4 @@ +import json from functools import singledispatch from django.conf import settings @@ -33,11 +34,34 @@ def to_representation(self, value): return reverse("template-download", args=[value.pk]) +class AvailablePlaceholdersField(serializers.ListField): + """A list field type that also accepts JSON lists. + + Instead of multiple fields with the same name (traditional + form-data lists), we also accept a JSON list for the available + placeholders. This helps reduce the number of fields in the + request, which WAF, Django, and possibly other server-side + web components don't appreciate. + """ + + def to_internal_value(self, data): + data = data if isinstance(data, list) else [data] + all_values = [] + for value in data: + if value.startswith("["): + # looks like JSON, parse it + all_values.extend(json.loads(value)) + else: + all_values.append(value) + + return all_values + + class TemplateSerializer(ValidatorMixin, serializers.ModelSerializer): disable_template_validation = serializers.BooleanField( allow_null=True, default=False ) - available_placeholders = serializers.ListField(allow_null=True, required=False) + available_placeholders = AvailablePlaceholdersField(allow_null=True, required=False) sample_data = serializers.JSONField(allow_null=True, required=False) files = serializers.ListField( child=CustomFileField(write_only=True, allow_empty_file=False), required=False diff --git a/document_merge_service/api/tests/test_template.py b/document_merge_service/api/tests/test_template.py index dcb48a6e..62ff9b3e 100644 --- a/document_merge_service/api/tests/test_template.py +++ b/document_merge_service/api/tests/test_template.py @@ -494,6 +494,7 @@ def test_disable_validation( ), ], ) +@pytest.mark.parametrize("use_json", [True, False]) def test_template_create_with_available_placeholders( db, admin_client, @@ -504,12 +505,18 @@ def test_template_create_with_available_placeholders( files, status_code, settings, + use_json, expect_missing_placeholders, ): settings.DOCXTEMPLATE_JINJA_EXTENSIONS = ["jinja2.ext.loopcontrols"] url = reverse("template-list") template_file = django_file(template_name) + + # files are being reused, so make sure they're readable + for f in files: + f.seek(0) + data = { "slug": "test-slug", "template": template_file.file, @@ -519,7 +526,9 @@ def test_template_create_with_available_placeholders( if sample_data: data["sample_data"] = json.dumps(sample_data) if available_placeholders: - data["available_placeholders"] = available_placeholders + data["available_placeholders"] = ( + json.dumps(available_placeholders) if use_json else available_placeholders + ) response = admin_client.post(url, data=data, format="multipart") assert response.status_code == status_code, response.json()