mirror of
https://github.com/django/django.git
synced 2025-11-19 03:08:59 +00:00
Fix RadioSelect showing blank option for required foreign keys
Prevent ModelForm RadioSelect widgets from displaying a blank "-------" option when the foreign key has blank=False. This ensures the form only shows valid choices and avoids user confusion. Closes # (if applicable)
This commit is contained in:
parent
335c9c94ac
commit
271666ad4a
2 changed files with 169 additions and 1 deletions
|
|
@ -13,7 +13,7 @@ from django.forms.forms import BaseForm, DeclarativeFieldsMetaclass
|
|||
from django.forms.formsets import BaseFormSet, formset_factory
|
||||
from django.forms.utils import ErrorList
|
||||
from django.forms.widgets import (
|
||||
HiddenInput, MultipleHiddenInput, SelectMultiple,
|
||||
HiddenInput, MultipleHiddenInput, RadioSelect, SelectMultiple,
|
||||
)
|
||||
from django.utils.text import capfirst, get_text_list
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
|
|
@ -1185,8 +1185,19 @@ class ModelChoiceField(ChoiceField):
|
|||
required=True, widget=None, label=None, initial=None,
|
||||
help_text='', to_field_name=None, limit_choices_to=None,
|
||||
**kwargs):
|
||||
# Determine whether to include the empty label based on:
|
||||
# 1. If required and initial is provided, never show empty label
|
||||
# 2. If required and widget is RadioSelect, never show empty label
|
||||
# (RadioSelect has a natural unfilled state)
|
||||
# 3. Otherwise, show the empty label
|
||||
if required and (initial is not None):
|
||||
self.empty_label = None
|
||||
elif required and isinstance(widget, type) and issubclass(widget, RadioSelect):
|
||||
# widget is a class (not yet instantiated)
|
||||
self.empty_label = None
|
||||
elif required and isinstance(widget, RadioSelect):
|
||||
# widget is an instance
|
||||
self.empty_label = None
|
||||
else:
|
||||
self.empty_label = empty_label
|
||||
|
||||
|
|
|
|||
157
tests/model_forms/test_radioselect_blank.py
Normal file
157
tests/model_forms/test_radioselect_blank.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
"""
|
||||
Tests for RadioSelect widget with ModelChoiceField to ensure blank options
|
||||
are not shown when the field is required.
|
||||
"""
|
||||
from django import forms
|
||||
from django.forms.widgets import RadioSelect, Select
|
||||
from django.test import TestCase
|
||||
|
||||
from .models import Category
|
||||
|
||||
|
||||
class RadioSelectBlankOptionTests(TestCase):
|
||||
"""
|
||||
Test that RadioSelect widgets for required ModelChoiceField don't show
|
||||
a blank option, while Select widgets still do.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.c1 = Category.objects.create(name='First', slug='first', url='first')
|
||||
cls.c2 = Category.objects.create(name='Second', slug='second', url='second')
|
||||
|
||||
def test_radioselect_required_no_blank_option(self):
|
||||
"""
|
||||
A required ModelChoiceField with RadioSelect widget should not include
|
||||
a blank option.
|
||||
"""
|
||||
field = forms.ModelChoiceField(
|
||||
queryset=Category.objects.all(),
|
||||
widget=RadioSelect,
|
||||
required=True
|
||||
)
|
||||
# The field should not have an empty_label
|
||||
self.assertIsNone(field.empty_label)
|
||||
|
||||
# Check choices don't include blank option
|
||||
choices = list(field.choices)
|
||||
self.assertEqual(len(choices), 2)
|
||||
self.assertEqual(choices[0][0], self.c1.pk)
|
||||
self.assertEqual(choices[1][0], self.c2.pk)
|
||||
|
||||
def test_radioselect_required_no_blank_option_instance(self):
|
||||
"""
|
||||
A required ModelChoiceField with an instantiated RadioSelect widget
|
||||
should not include a blank option.
|
||||
"""
|
||||
field = forms.ModelChoiceField(
|
||||
queryset=Category.objects.all(),
|
||||
widget=RadioSelect(),
|
||||
required=True
|
||||
)
|
||||
# The field should not have an empty_label
|
||||
self.assertIsNone(field.empty_label)
|
||||
|
||||
# Check choices don't include blank option
|
||||
choices = list(field.choices)
|
||||
self.assertEqual(len(choices), 2)
|
||||
self.assertEqual(choices[0][0], self.c1.pk)
|
||||
self.assertEqual(choices[1][0], self.c2.pk)
|
||||
|
||||
def test_radioselect_not_required_has_blank_option(self):
|
||||
"""
|
||||
A non-required ModelChoiceField with RadioSelect widget should still
|
||||
include a blank option.
|
||||
"""
|
||||
field = forms.ModelChoiceField(
|
||||
queryset=Category.objects.all(),
|
||||
widget=RadioSelect,
|
||||
required=False
|
||||
)
|
||||
# The field should have an empty_label
|
||||
self.assertEqual(field.empty_label, '---------')
|
||||
|
||||
# Check choices include blank option
|
||||
choices = list(field.choices)
|
||||
self.assertEqual(len(choices), 3)
|
||||
self.assertEqual(choices[0][0], '')
|
||||
self.assertEqual(choices[0][1], '---------')
|
||||
|
||||
def test_select_required_has_blank_option(self):
|
||||
"""
|
||||
A required ModelChoiceField with Select widget should still include
|
||||
a blank option (existing behavior should be preserved).
|
||||
"""
|
||||
field = forms.ModelChoiceField(
|
||||
queryset=Category.objects.all(),
|
||||
widget=Select,
|
||||
required=True
|
||||
)
|
||||
# The field should have an empty_label for Select widget
|
||||
self.assertEqual(field.empty_label, '---------')
|
||||
|
||||
# Check choices include blank option
|
||||
choices = list(field.choices)
|
||||
self.assertEqual(len(choices), 3)
|
||||
self.assertEqual(choices[0][0], '')
|
||||
self.assertEqual(choices[0][1], '---------')
|
||||
|
||||
def test_select_no_widget_required_has_blank_option(self):
|
||||
"""
|
||||
A required ModelChoiceField with no explicit widget (defaults to Select)
|
||||
should include a blank option (existing behavior should be preserved).
|
||||
"""
|
||||
field = forms.ModelChoiceField(
|
||||
queryset=Category.objects.all(),
|
||||
required=True
|
||||
)
|
||||
# The field should have an empty_label for default Select widget
|
||||
self.assertEqual(field.empty_label, '---------')
|
||||
|
||||
# Check choices include blank option
|
||||
choices = list(field.choices)
|
||||
self.assertEqual(len(choices), 3)
|
||||
self.assertEqual(choices[0][0], '')
|
||||
self.assertEqual(choices[0][1], '---------')
|
||||
|
||||
def test_radioselect_required_with_initial_no_blank_option(self):
|
||||
"""
|
||||
A required ModelChoiceField with RadioSelect widget and initial value
|
||||
should not include a blank option.
|
||||
"""
|
||||
field = forms.ModelChoiceField(
|
||||
queryset=Category.objects.all(),
|
||||
widget=RadioSelect,
|
||||
required=True,
|
||||
initial=self.c1
|
||||
)
|
||||
# The field should not have an empty_label
|
||||
self.assertIsNone(field.empty_label)
|
||||
|
||||
# Check choices don't include blank option
|
||||
choices = list(field.choices)
|
||||
self.assertEqual(len(choices), 2)
|
||||
self.assertEqual(choices[0][0], self.c1.pk)
|
||||
self.assertEqual(choices[1][0], self.c2.pk)
|
||||
|
||||
def test_radioselect_custom_empty_label(self):
|
||||
"""
|
||||
A non-required ModelChoiceField with RadioSelect widget and custom
|
||||
empty_label should use the custom label.
|
||||
"""
|
||||
field = forms.ModelChoiceField(
|
||||
queryset=Category.objects.all(),
|
||||
widget=RadioSelect,
|
||||
required=False,
|
||||
empty_label='Select one...'
|
||||
)
|
||||
# The field should have the custom empty_label
|
||||
self.assertEqual(field.empty_label, 'Select one...')
|
||||
|
||||
# Check choices include custom blank option
|
||||
choices = list(field.choices)
|
||||
self.assertEqual(len(choices), 3)
|
||||
self.assertEqual(choices[0][0], '')
|
||||
self.assertEqual(choices[0][1], 'Select one...')
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue