diff --git a/src/pretix/api/serializers/organizer.py b/src/pretix/api/serializers/organizer.py index ba51524b7d..7e83daf82f 100644 --- a/src/pretix/api/serializers/organizer.py +++ b/src/pretix/api/serializers/organizer.py @@ -657,11 +657,24 @@ class EventMetaPropertiesSerializer(I18nAwareModelSerializer): full_data.update(data) choices = full_data.get("choices") - if choices is not None and not isinstance(choices, dict): - raise ValidationError("Choices need to be a dictionary or null.") - default = full_data.get("default") - if choices and default and default not in choices.keys(): - raise ValidationError("You cannot set a default value that is not a valid value.") + if "choices" in data and data["choices"] is not None: + if not isinstance(choices, list): + raise ValidationError("Choices need to be a list or null.") + if any([not isinstance(c, dict) for c in choices]): + raise ValidationError("Choices need to contain only objects.") + required_keys = {"key"} + allowed_keys = {"key", "label"} + if not all([required_keys <= set(c.keys()) <= allowed_keys for c in choices]): + raise ValidationError("Each choice must contain a key and optionally a label.") + + if choices: + choice_keys = [c.get("key") for c in choices] + if len(set(choice_keys)) < len(choice_keys): + raise ValidationError("Each choice must have a unique key.") + + default = full_data.get("default") + if default and default not in choice_keys: + raise ValidationError("You cannot set a default value that is not a valid value.") if not choices and "choices" in data: # normalize empty dict to None diff --git a/src/tests/api/test_event_meta_properties.py b/src/tests/api/test_event_meta_properties.py index 9073323f87..87975a4471 100644 --- a/src/tests/api/test_event_meta_properties.py +++ b/src/tests/api/test_event_meta_properties.py @@ -82,7 +82,6 @@ def test_meta_property_create(token_client, organizer): } ) assert resp.status_code == 400 - choices = {"r": "Red", "g": "Green", "b": "Blue"} resp = token_client.post( '/api/v1/organizers/{}/event_meta_properties/'.format(organizer.slug), format='json', @@ -90,6 +89,22 @@ def test_meta_property_create(token_client, organizer): "name": "Color", "default": "Red", "required": False, + "choices": {"key": "r", "label": "Red"}, + } + ) + assert resp.status_code == 400 + choices = [ + {"key": "r", "label": "Red"}, + {"key": "g", "label": "Green"}, + {"key": "b", "label": "Blue"}, + ] + resp = token_client.post( + '/api/v1/organizers/{}/event_meta_properties/'.format(organizer.slug), + format='json', + data={ + "name": "Color", + "default": "k", + "required": False, "choices": choices, } ) @@ -122,7 +137,7 @@ def test_meta_property_patch(token_client, organizer, event_meta_property): format='json', data={ # existing default is not in choices - "choices": {'k': 'Black'}, + "choices": [{'k': 'Black'}], } ) assert resp.status_code == 400