From cc77c105b79cc653e4912f37ead49d35a41d733c Mon Sep 17 00:00:00 2001 From: fbz Date: Sat, 5 Jul 2025 04:16:36 +0800 Subject: [PATCH 1/6] fix: MultipleChoiceField use ordered sort (cherry picked from commit 8436483e66af3d1317d99335b7fae95c1f58d13a) --- rest_framework/fields.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 89c0a714c..d5d828c39 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1469,17 +1469,17 @@ class MultipleChoiceField(ChoiceField): if not self.allow_empty and len(data) == 0: self.fail('empty') - return { - # Arguments for super() are needed because of scoping inside - # comprehensions. + # Arguments for super() are needed because of scoping inside + # comprehensions. + return list(dict.fromkeys([ super(MultipleChoiceField, self).to_internal_value(item) for item in data - } + ])) def to_representation(self, value): - return { + return list(dict.fromkeys([ self.choice_strings_to_values.get(str(item), item) for item in value - } + ])) class FilePathField(ChoiceField): From 3e09113622da3d04f08a5aa97103baadf422c8ad Mon Sep 17 00:00:00 2001 From: fbz Date: Sat, 5 Jul 2025 17:13:24 +0800 Subject: [PATCH 2/6] test: fix unit tests (cherry picked from commit 6428ac4a05f4a33eb0813cc56d584f56e7bfac89) --- tests/test_fields.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index d574b07eb..c5cd62173 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -2056,16 +2056,18 @@ class TestMultipleChoiceField(FieldValues): Valid and invalid values for `MultipleChoiceField`. """ valid_inputs = { - (): set(), - ('aircon',): {'aircon'}, - ('aircon', 'manual'): {'aircon', 'manual'}, + (): list(), + ("aircon",): ["aircon"], + ("aircon", "manual"): ["aircon", "manual"], + ("manual", "aircon"): ["manual", "aircon"], } invalid_inputs = { 'abc': ['Expected a list of items but got type "str".'], ('aircon', 'incorrect'): ['"incorrect" is not a valid choice.'] } outputs = [ - (['aircon', 'manual', 'incorrect'], {'aircon', 'manual', 'incorrect'}) + (["aircon", "manual", "incorrect"], ["aircon", "manual", "incorrect"]), + (["manual", "aircon", "incorrect"], ["manual", "aircon", "incorrect"]), ] field = serializers.MultipleChoiceField( choices=[ From 0ee24d571646e472283b6ddb6b33ac996185fb2d Mon Sep 17 00:00:00 2001 From: fbz Date: Sat, 5 Jul 2025 17:22:08 +0800 Subject: [PATCH 3/6] test: test TestMultipleChoiceField can json serializable (cherry picked from commit 12908b149c446598682269f8df78290fa8268982) --- tests/test_fields.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index c5cd62173..2040a59d0 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -11,6 +11,8 @@ from zoneinfo import ZoneInfo import pytest +from rest_framework.utils import json + try: import pytz except ImportError: @@ -2082,7 +2084,30 @@ class TestMultipleChoiceField(FieldValues): field.partial = False assert field.get_value(QueryDict('')) == [] field.partial = True - assert field.get_value(QueryDict('')) == rest_framework.fields.empty + assert field.get_value(QueryDict("")) == rest_framework.fields.empty + + def test_valid_inputs_is_json_serializable(self): + for input_value, _ in get_items(self.valid_inputs): + validated = self.field.run_validation(input_value) + + try: + json.dumps(validated) + except TypeError as e: + assert ( + False + ), f"Validated output not JSON serializable: {repr(validated)}; Error: {e}" + + def test_output_is_json_serializable(self): + for output_value, _ in get_items(self.outputs): + representation = self.field.to_representation(output_value) + + try: + json.dumps(representation) + except TypeError as e: + assert False, ( + f"to_representation output not JSON serializable: " + f"{repr(representation)}; Error: {e}" + ) class TestEmptyMultipleChoiceField(FieldValues): From cfd4412a1eb35ba748e47888b03f9374bca41713 Mon Sep 17 00:00:00 2001 From: fbz Date: Sat, 5 Jul 2025 17:38:15 +0800 Subject: [PATCH 4/6] test: fix unit test (cherry picked from commit 73a709c4b04ae510d61f2d426f93f6aef98b09fd) --- tests/test_serializer_nested.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_serializer_nested.py b/tests/test_serializer_nested.py index b7195494c..4a71dcbcc 100644 --- a/tests/test_serializer_nested.py +++ b/tests/test_serializer_nested.py @@ -199,14 +199,14 @@ class TestNestedSerializerWithList: serializer = self.Serializer(data=input_data) assert serializer.is_valid() - assert serializer.validated_data['nested']['example'] == {1, 2} + assert serializer.validated_data['nested']['example'] == [1, 2] def test_nested_serializer_with_list_multipart(self): input_data = QueryDict('nested.example=1&nested.example=2') serializer = self.Serializer(data=input_data) assert serializer.is_valid() - assert serializer.validated_data['nested']['example'] == {1, 2} + assert serializer.validated_data['nested']['example'] == [1, 2] class TestNotRequiredNestedSerializerWithMany: From 1977c5b5753e10d4c9ef047c3e809c57ba10a9af Mon Sep 17 00:00:00 2001 From: fbz Date: Sun, 6 Jul 2025 00:30:21 +0800 Subject: [PATCH 5/6] minor: rest old formatting --- tests/test_fields.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index 2040a59d0..cb4560100 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -2059,17 +2059,17 @@ class TestMultipleChoiceField(FieldValues): """ valid_inputs = { (): list(), - ("aircon",): ["aircon"], - ("aircon", "manual"): ["aircon", "manual"], - ("manual", "aircon"): ["manual", "aircon"], + ('aircon',): ['aircon'], + ('aircon', 'manual'): ['aircon', 'manual'], + ('manual', 'aircon'): ['manual', 'aircon'], } invalid_inputs = { 'abc': ['Expected a list of items but got type "str".'], ('aircon', 'incorrect'): ['"incorrect" is not a valid choice.'] } outputs = [ - (["aircon", "manual", "incorrect"], ["aircon", "manual", "incorrect"]), - (["manual", "aircon", "incorrect"], ["manual", "aircon", "incorrect"]), + (['aircon', 'manual', 'incorrect'], ['aircon', 'manual', 'incorrect']), + (['manual', 'aircon', 'incorrect'], ['manual', 'aircon', 'incorrect']), ] field = serializers.MultipleChoiceField( choices=[ @@ -2084,7 +2084,7 @@ class TestMultipleChoiceField(FieldValues): field.partial = False assert field.get_value(QueryDict('')) == [] field.partial = True - assert field.get_value(QueryDict("")) == rest_framework.fields.empty + assert field.get_value(QueryDict('')) == rest_framework.fields.empty def test_valid_inputs_is_json_serializable(self): for input_value, _ in get_items(self.valid_inputs): @@ -2095,7 +2095,7 @@ class TestMultipleChoiceField(FieldValues): except TypeError as e: assert ( False - ), f"Validated output not JSON serializable: {repr(validated)}; Error: {e}" + ), f'Validated output not JSON serializable: {repr(validated)}; Error: {e}' def test_output_is_json_serializable(self): for output_value, _ in get_items(self.outputs): @@ -2105,8 +2105,8 @@ class TestMultipleChoiceField(FieldValues): json.dumps(representation) except TypeError as e: assert False, ( - f"to_representation output not JSON serializable: " - f"{repr(representation)}; Error: {e}" + f'to_representation output not JSON serializable: ' + f'{repr(representation)}; Error: {e}' ) From 40465d95c5c21d1ec47c60dd2eb339411271538c Mon Sep 17 00:00:00 2001 From: fbz Date: Sun, 6 Jul 2025 16:24:52 +0800 Subject: [PATCH 6/6] fix: using pytest.fail to test --- tests/test_fields.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index cb4560100..0b6f4945c 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -2093,9 +2093,7 @@ class TestMultipleChoiceField(FieldValues): try: json.dumps(validated) except TypeError as e: - assert ( - False - ), f'Validated output not JSON serializable: {repr(validated)}; Error: {e}' + pytest.fail(f'Validated output not JSON serializable: {repr(validated)}; Error: {e}') def test_output_is_json_serializable(self): for output_value, _ in get_items(self.outputs): @@ -2104,7 +2102,7 @@ class TestMultipleChoiceField(FieldValues): try: json.dumps(representation) except TypeError as e: - assert False, ( + pytest.fail( f'to_representation output not JSON serializable: ' f'{repr(representation)}; Error: {e}' )