From fa16bbb04c45b41bc0c4d54d1daf01cf015a7d44 Mon Sep 17 00:00:00 2001 From: Nicolas Delaby Date: Tue, 24 Mar 2026 10:39:57 +0100 Subject: [PATCH 1/3] Include choices params even for non editable fields The choices param can be usefull to populate list of possible values and to instantiate a ChoiceField on the serializer, even when field is non_editable. Relevant for the live doc and the OpenAPI integration. --- rest_framework/utils/field_mapping.py | 7 ++++--- tests/test_model_serializer.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index 15c4b91055..d35caca0c7 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -121,6 +121,9 @@ def get_field_kwargs(field_name, model_field): if model_field.null: kwargs['allow_null'] = True + if model_field.choices: + kwargs['choices'] = model_field.choices + if isinstance(model_field, models.AutoField) or not model_field.editable: # If this field is read-only, then return early. # Further keyword arguments are not valid. @@ -151,9 +154,7 @@ def get_field_kwargs(field_name, model_field): if model_field.allow_folders is not False: kwargs['allow_folders'] = model_field.allow_folders - if model_field.choices: - kwargs['choices'] = model_field.choices - else: + if not model_field.choices: # Ensure that max_value is passed explicitly as a keyword arg, # rather than as a validator. max_value = next(( diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 60820df4f4..1590ee2260 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -25,6 +25,7 @@ from rest_framework import serializers from rest_framework.compat import postgres_fields +from rest_framework.fields import ChoiceField from .models import NestedForeignKeySource @@ -95,6 +96,7 @@ class FieldOptionsModel(models.Model): class ChoicesModel(models.Model): choices_field_with_nonstandard_args = models.DecimalField(max_digits=3, decimal_places=1, choices=DECIMAL_CHOICES, verbose_name='A label') + non_editable_choice_field = models.CharField(choices=COLOR_CHOICES, default=COLOR_CHOICES[0][0], editable=False, max_length=5) class Issue3674ParentModel(models.Model): @@ -362,6 +364,16 @@ class Meta: ExampleSerializer() + def test_non_editable_choice_field(self): + class ExampleSerializer(serializers.ModelSerializer): + class Meta: + model = ChoicesModel + fields = '__all__' + + serializer = ExampleSerializer() + non_editable_choice_field = serializer.get_fields()["non_editable_choice_field"] + assert isinstance(non_editable_choice_field, ChoiceField) + class TestDurationFieldMapping(TestCase): def test_duration_field(self): From cc962a280d8a470143a3f482c9a2b32c807a129d Mon Sep 17 00:00:00 2001 From: Nicolas Delaby Date: Thu, 26 Mar 2026 15:06:04 +0100 Subject: [PATCH 2/3] Consistency and expliciteness --- tests/test_model_serializer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 1590ee2260..13eca71964 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -371,8 +371,9 @@ class Meta: fields = '__all__' serializer = ExampleSerializer() - non_editable_choice_field = serializer.get_fields()["non_editable_choice_field"] + non_editable_choice_field = serializer.get_fields()['non_editable_choice_field'] assert isinstance(non_editable_choice_field, ChoiceField) + assert non_editable_choice_field.read_only is True class TestDurationFieldMapping(TestCase): From 3da5927899bef34d712617e5cfc6a589e8309cb0 Mon Sep 17 00:00:00 2001 From: Nicolas Delaby Date: Thu, 26 Mar 2026 16:23:16 +0100 Subject: [PATCH 3/3] extend test assertions --- tests/test_model_serializer.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 13eca71964..944c1ba278 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -362,7 +362,11 @@ class Meta: model = ChoicesModel fields = '__all__' - ExampleSerializer() + serializer = ExampleSerializer() + choices_field_with_nonstandard_args = serializer.get_fields()['choices_field_with_nonstandard_args'] + assert isinstance(choices_field_with_nonstandard_args, ChoiceField) + assert choices_field_with_nonstandard_args.choices + assert choices_field_with_nonstandard_args.read_only is False def test_non_editable_choice_field(self): class ExampleSerializer(serializers.ModelSerializer): @@ -374,6 +378,7 @@ class Meta: non_editable_choice_field = serializer.get_fields()['non_editable_choice_field'] assert isinstance(non_editable_choice_field, ChoiceField) assert non_editable_choice_field.read_only is True + assert non_editable_choice_field.choices class TestDurationFieldMapping(TestCase):