From ab248d0d97bfe0a1d1031c49f5d6f4984c62739b Mon Sep 17 00:00:00 2001 From: Richard Schreiber Date: Tue, 22 Sep 2026 11:38:51 +0200 Subject: [PATCH] Change to I18nField for validation --- src/pretix/api/serializers/organizer.py | 41 +++++++++++---------- src/tests/api/test_event_meta_properties.py | 14 ++++--- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/pretix/api/serializers/organizer.py b/src/pretix/api/serializers/organizer.py index 4a43e421b9..5fe450b722 100644 --- a/src/pretix/api/serializers/organizer.py +++ b/src/pretix/api/serializers/organizer.py @@ -28,6 +28,7 @@ from django.db import transaction from django.db.models import Q from django.utils.crypto import get_random_string from django.utils.translation import gettext, gettext_lazy as _ +from i18nfield.rest_framework import I18nField from rest_framework import serializers from rest_framework.exceptions import ValidationError @@ -661,9 +662,20 @@ class MetaPropertyListField(serializers.ListField): class MetaPropertyDictField(serializers.DictField): + child_per_key = { + "label": I18nField() + } + def to_representation(self, value): # django added unneccessary keys DELETE, ORDER through formsets, filter them here for backwards compat - return super().to_representation({k: value[k] for k in ("key", "label") if k in value}) + d = { + "key": value["key"] + } + if "label" in value: + f = I18nField() + d["label"] = f.to_representation(value["label"]) + + return super().to_representation(d) def to_internal_value(self, data): if not isinstance(data, dict): @@ -675,30 +687,19 @@ class MetaPropertyDictField(serializers.DictField): if any(k not in {"key", "label"} for k in data.keys()): raise ValidationError("Meta properties may only have a key and optionally a label.") + if "label" in data: + f = I18nField() + try: + data["label"] = f.to_internal_value(data["label"]) + except ValidationError as e: + raise ValidationError({"label": e.detail}) + return super().to_internal_value(data) -class I18nField(serializers.Field): - def to_representation(self, value): - return value - - def to_internal_value(self, data): - if isinstance(data, str): - return data - if not isinstance(data, dict): - raise ValidationError("Must either be a string or a dict.") - if not all(isinstance(k, str) for k in data.keys()): - raise ValidationError("All keys must be strings.") - if not all(isinstance(v, str) for v in data.values()): - raise ValidationError("All values must be strings.") - - class EventMetaPropertiesSerializer(I18nAwareModelSerializer): choices = MetaPropertyListField( - child=MetaPropertyDictField( - # careful: this only works because I18nField allows plain strings (e.g. keys need to be plain strings) - child=I18nField() - ), + child=MetaPropertyDictField(), allow_null=True, ) diff --git a/src/tests/api/test_event_meta_properties.py b/src/tests/api/test_event_meta_properties.py index c3ff640cad..ebd6924da1 100644 --- a/src/tests/api/test_event_meta_properties.py +++ b/src/tests/api/test_event_meta_properties.py @@ -21,6 +21,7 @@ # import pytest from django_scopes import scopes_disabled +from i18nfield.strings import LazyI18nString @pytest.fixture @@ -32,7 +33,7 @@ def event_meta_property(organizer): choices=[ { "key": "Red", - "label": "Rot", + "label": LazyI18nString("Rot"), "DELETE": False, "ORDER": 1, } @@ -44,7 +45,7 @@ TEST_TYPE_RES = { "name": "Color", "default": "Red", "required": False, - "choices": [{"key": "Red", "label": "Rot"}], + "choices": [{"key": "Red", "label": {"en": "Rot"}}], 'filter_allowed': True, 'filter_public': False, 'protected': False, @@ -120,8 +121,8 @@ def test_meta_property_create(token_client, organizer): "default": "r", "required": False, "choices": [ - {"key": "r", "label": "Red"}, - {"key": "r", "label": "Razzmatazz"}, + {"key": "r", "label": {"en": "Red"}}, + {"key": "r", "label": {"en": "Razzmatazz"}}, ], } ) @@ -192,8 +193,9 @@ def test_meta_property_patch(token_client, organizer, event_meta_property): } ) assert resp.status_code == 400 - assert str(resp.data["choices"][0]["label"][0]) == "Must either be a string or a dict." - assert str(resp.data["choices"][1]["label"][0]) == "All values must be strings." + #assert resp.data == 123 + assert str(resp.data["choices"][0]["label"][0]) == "Invalid data type." + assert str(resp.data["choices"][1]["label"][0]) == "All entries must be strings." resp = token_client.patch(