diff options
31 files changed, 276 insertions, 79 deletions
diff --git a/django/conf/__init__.py b/django/conf/__init__.py index c7ae36aba0..25f5ffa305 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -16,12 +16,19 @@ from pathlib import Path import django from django.conf import global_settings from django.core.exceptions import ImproperlyConfigured +from django.utils.deprecation import RemovedInDjango70Warning, django_file_prefixes from django.utils.functional import LazyObject, empty ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" DEFAULT_STORAGE_ALIAS = "default" STATICFILES_STORAGE_ALIAS = "staticfiles" +USE_BLANK_CHOICE_DASH_DEPRECATED_MSG = ( + "The USE_BLANK_CHOICE_DASH setting is deprecated. If you wish to define " + "your own default blank choice label, override " + "django.db.models.fields.BLANK_CHOICE_LABEL in your app's ready() method." +) + class SettingsReference(str): """ @@ -226,6 +233,12 @@ class UserSettingsHolder: def __setattr__(self, name, value): self._deleted.discard(name) + if name == "USE_BLANK_CHOICE_DASH": + warnings.warn( + USE_BLANK_CHOICE_DASH_DEPRECATED_MSG, + RemovedInDjango70Warning, + skip_file_prefixes=django_file_prefixes(), + ) super().__setattr__(name, value) def __delattr__(self, name): diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 72c376dd78..b2d07cffba 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -218,6 +218,9 @@ TEMPLATES = [] # Default form rendering class. FORM_RENDERER = "django.forms.renderers.DjangoTemplates" +# RemovedInDjango70Warning: This setting allows to revert back to the old +# blank choice label in Django 6.1. +USE_BLANK_CHOICE_DASH = False # Default email address to use for various automated correspondence from # the site managers. diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index bb091e4c52..71d4a2d55c 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -43,6 +43,7 @@ from django.core.exceptions import ( from django.core.paginator import Paginator from django.db import models, router, transaction from django.db.models.constants import LOOKUP_SEP +from django.db.models.utils import get_blank_choice_label from django.forms.formsets import DELETION_FIELD_NAME, all_valid from django.forms.models import ( BaseInlineFormSet, @@ -1049,11 +1050,13 @@ class ModelAdmin(BaseModelAdmin): actions = self._filter_actions_by_permissions(request, self._get_base_actions()) return {name: (func, name, desc) for func, name, desc in actions} - def get_action_choices(self, request, default_choices=models.BLANK_CHOICE_DASH): + def get_action_choices(self, request, default_choices=None): """ Return a list of choices for use in a form object. Each choice is a tuple (name, description). """ + if default_choices is None: + default_choices = [("", get_blank_choice_label())] choices = [*default_choices] for func, name, description in self.get_actions(request).values(): choice = (name, description % model_format_dict(self.opts)) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index e248b70ba3..a7ec41bf75 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -15,6 +15,7 @@ from django.core import checks, exceptions, validators from django.db import connection, connections, router from django.db.models.constants import LOOKUP_SEP from django.db.models.query_utils import DeferredAttribute, RegisterLookupMixin +from django.db.models.utils import get_blank_choice_label from django.db.utils import NotSupportedError from django.utils import timezone from django.utils.choices import ( @@ -39,7 +40,9 @@ from django.utils.translation import gettext_lazy as _ __all__ = [ "AutoField", + # RemovedInDjango70Warning "BLANK_CHOICE_DASH", + "BLANK_CHOICE_LABEL", "BigAutoField", "BigIntegerField", "BinaryField", @@ -81,9 +84,13 @@ class NOT_PROVIDED: pass -# The values to use for "blank" in SelectFields. Will be appended to the start -# of most "choices" lists. +# RemovedInDjango70Warning: From Django 6.1, the values to use for "blank" +# in SelectFields will be defined by the below BLANK_CHOICE_LABEL constant. +# Will be appended to the start of most "choices" lists. +# BLANK_CHOICE_DASH is still available as a constant in Django 6.1. BLANK_CHOICE_DASH = [("", "---------")] +# This allows any app's ready() method to overwrite BLANK_CHOICE_LABEL. +BLANK_CHOICE_LABEL = _("- Select an option -") def _load_field(app_label, model_name, field_name): @@ -1088,7 +1095,7 @@ class Field(RegisterLookupMixin): def get_choices( self, include_blank=True, - blank_choice=BLANK_CHOICE_DASH, + blank_choice=None, limit_choices_to=None, ordering=(), ): @@ -1096,6 +1103,8 @@ class Field(RegisterLookupMixin): Return choices with a default blank choices included, for use as <select> choices for this field. """ + if blank_choice is None: + blank_choice = [("", get_blank_choice_label())] if self.choices is not None: if include_blank: return BlankChoiceIterator(self.choices, blank_choice) diff --git a/django/db/models/fields/reverse_related.py b/django/db/models/fields/reverse_related.py index e6c2525115..df50ba436f 100644 --- a/django/db/models/fields/reverse_related.py +++ b/django/db/models/fields/reverse_related.py @@ -13,7 +13,7 @@ from django.core import exceptions from django.utils.functional import cached_property from django.utils.hashable import make_hashable -from . import BLANK_CHOICE_DASH +from ..utils import get_blank_choice_label from .mixins import FieldCacheMixin @@ -172,7 +172,7 @@ class ForeignObjectRel(FieldCacheMixin): def get_choices( self, include_blank=True, - blank_choice=BLANK_CHOICE_DASH, + blank_choice=None, limit_choices_to=None, ordering=(), ): @@ -183,6 +183,8 @@ class ForeignObjectRel(FieldCacheMixin): Analog of django.db.models.fields.Field.get_choices(), provided initially for utilization by RelatedFieldListFilter. """ + if blank_choice is None: + blank_choice = [("", get_blank_choice_label())] limit_choices_to = limit_choices_to or self.limit_choices_to qs = self.related_model._default_manager.complex_filter(limit_choices_to) if ordering: diff --git a/django/db/models/utils.py b/django/db/models/utils.py index c6cb5ef165..7f38bd7afb 100644 --- a/django/db/models/utils.py +++ b/django/db/models/utils.py @@ -67,3 +67,15 @@ class AltersData: break super().__init_subclass__(**kwargs) + + +# RemovedInDjango70Warning: At the end of the deprecation, remove this function +# and use .fields.BLANK_CHOICE_LABEL directly instead. +def get_blank_choice_label(): + from django.conf import settings + + from .fields import BLANK_CHOICE_DASH, BLANK_CHOICE_LABEL + + if settings.USE_BLANK_CHOICE_DASH: + return BLANK_CHOICE_DASH[0][1] + return BLANK_CHOICE_LABEL diff --git a/django/forms/fields.py b/django/forms/fields.py index 8aad2d48b8..26640ed7d3 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -15,6 +15,7 @@ from io import BytesIO from django.core import validators from django.core.exceptions import ValidationError +from django.db.models.utils import get_blank_choice_label from django.forms.boundfield import BoundField from django.forms.utils import from_current_timezone, to_current_timezone from django.forms.widgets import ( @@ -1200,7 +1201,7 @@ class FilePathField(ChoiceField): if self.required: self.choices = [] else: - self.choices = [("", "---------")] + self.choices = [("", get_blank_choice_label())] if self.match is not None: self.match_re = re.compile(self.match) diff --git a/django/forms/models.py b/django/forms/models.py index a53f119995..9686baa6f2 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -12,7 +12,7 @@ from django.core.exceptions import ( ValidationError, ) from django.core.validators import ProhibitNullCharactersValidator -from django.db.models.utils import AltersData +from django.db.models.utils import AltersData, get_blank_choice_label from django.forms.fields import ChoiceField, Field from django.forms.forms import BaseForm, DeclarativeFieldsMetaclass from django.forms.formsets import BaseFormSet, formset_factory @@ -1481,7 +1481,7 @@ class ModelChoiceField(ChoiceField): self, queryset, *, - empty_label="---------", + empty_label="", required=True, widget=None, label=None, @@ -1508,6 +1508,8 @@ class ModelChoiceField(ChoiceField): ): self.empty_label = None else: + if empty_label == "": + empty_label = get_blank_choice_label() self.empty_label = empty_label self.queryset = queryset self.limit_choices_to = limit_choices_to # limit the queryset later. diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 799c355334..c8e467a0ee 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -64,6 +64,10 @@ details on these changes. :class:`~django.db.models.JSONNull` to query for a JSON ``null`` value instead. +* The ``django.db.models.fields.BLANK_CHOICE_DASH`` constant will be removed. + +* The ``USE_BLANK_CHOICE_DASH`` transitional setting will be removed. + * The ``Field.get_placeholder_sql`` shim over the deprecated ``get_placeholder`` method will be removed. diff --git a/docs/intro/_images/admin09.png b/docs/intro/_images/admin09.png Binary files differindex cf2e9a9e1c..5357bb99cf 100644 --- a/docs/intro/_images/admin09.png +++ b/docs/intro/_images/admin09.png diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 73faa8cc18..085b36a0e5 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -1384,7 +1384,7 @@ generating choices. See :ref:`iterating-relationship-choices` for details. By default the ``<select>`` widget used by ``ModelChoiceField`` will have an empty choice at the top of the list. You can change the text of - this label (which is ``"---------"`` by default) with the + this label (which is ``"- Select an option -"`` by default) with the ``empty_label`` attribute, or you can disable the empty label entirely by setting ``empty_label`` to ``None``:: @@ -1400,6 +1400,11 @@ generating choices. See :ref:`iterating-relationship-choices` for details. :class:`~django.forms.RadioSelect` and the :attr:`~ModelChoiceField.blank` argument is ``False``. + .. versionchanged:: 6.1 + + The default empty label was changed from ``"---------"`` to + ``"- Select an option -"``. + .. attribute:: to_field_name This optional argument is used to specify the field to use as the value diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 1cf521d621..4405d91daf 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -224,11 +224,16 @@ documentation. .. _field-choices-blank-label: Unless :attr:`blank=False<Field.blank>` is set on the field along with a -:attr:`~Field.default` then a label containing ``"---------"`` will be rendered -with the select box. To override this behavior, add a tuple to ``choices`` -containing ``None``; e.g. ``(None, 'Your String For Display')``. -Alternatively, you can use an empty string instead of ``None`` where this makes -sense - such as on a :class:`~django.db.models.CharField`. +:attr:`~Field.default` then a label containing ``"- Select an option -"`` will +be rendered with the select box. To override this behavior, add a tuple to +``choices`` containing ``None``; e.g. ``(None, 'Your String For Display')``. +Alternatively, you can use an empty string instead of ``None`` where this +makes sense - such as on a :class:`~django.db.models.CharField`. + +.. versionchanged:: 6.1 + + The default blank label was changed from ``"---------"`` to + ``"- Select an option -"``. .. _field-choices-enum-types: diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index cca7b18f24..c4e2c6f2c3 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -3044,6 +3044,19 @@ the correct environment. .. _list of time zones: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +.. setting:: USE_BLANK_CHOICE_DASH + +``USE_BLANK_CHOICE_DASH`` +------------------------------------ + +.. versionadded:: 6.1 +.. deprecated:: 6.1 + +Default: ``False`` + +Set this transitional setting to ``True`` to revert back to the old default +blank choice label in forms. + .. setting:: USE_I18N ``USE_I18N`` @@ -3851,6 +3864,7 @@ File uploads Forms ----- * :setting:`FORM_RENDERER` +* :setting:`USE_BLANK_CHOICE_DASH` Globalization (``i18n``/``l10n``) --------------------------------- diff --git a/docs/releases/6.1.txt b/docs/releases/6.1.txt index 142ddb631e..a4e10d2102 100644 --- a/docs/releases/6.1.txt +++ b/docs/releases/6.1.txt @@ -244,7 +244,11 @@ File Uploads Forms ~~~~~ -* ... +* The new constant ``django.db.models.fields.BLANK_CHOICE_LABEL`` defines a + more accessible and translatable default label for the blank choice in + forms, which is appended to most ``choices`` lists. The transitional setting + :setting:`USE_BLANK_CHOICE_DASH` allows you to revert back to the old + default label. Generic Views ~~~~~~~~~~~~~ @@ -540,6 +544,11 @@ Miscellaneous used as the top-level value. :lookup:`Key and index lookups <jsonfield.key>` are unaffected by this deprecation. +* The ``django.db.models.fields.BLANK_CHOICE_DASH`` constant is deprecated + in favor of the new constant ``django.db.models.fields.BLANK_CHOICE_LABEL``. + +* The :setting:`USE_BLANK_CHOICE_DASH` transitional setting is deprecated. + * The undocumented ``get_placeholder`` method of :class:`~django.db.models.Field` is deprecated in favor of the newly introduced ``get_placeholder_sql`` method, which has the same input signature diff --git a/tests/admin_views/test_actions.py b/tests/admin_views/test_actions.py index 467fe046ef..c2c18e0b74 100644 --- a/tests/admin_views/test_actions.py +++ b/tests/admin_views/test_actions.py @@ -294,7 +294,7 @@ class AdminActionsTest(TestCase): self.assertContains( response, """<label>Action: <select name="action" required> -<option value="" selected>---------</option> +<option value="" selected>- Select an option -</option> <option value="delete_selected">Delete selected external subscribers</option> <option value="redirect_to">Redirect to (Awesome action)</option> diff --git a/tests/admin_views/test_related_object_lookups.py b/tests/admin_views/test_related_object_lookups.py index 2e6c0d07bd..eeba23f4f3 100644 --- a/tests/admin_views/test_related_object_lookups.py +++ b/tests/admin_views/test_related_object_lookups.py @@ -112,7 +112,7 @@ class SeleniumTests(AdminSeleniumTestCase): self.assertHTMLEqual( fk_dropdown.get_attribute("innerHTML"), f""" - <option value="" selected="">---------</option> + <option value="" selected="">- Select an option -</option> <option value="{id_value}" selected>{interesting_name}</option> """, ) @@ -157,7 +157,7 @@ class SeleniumTests(AdminSeleniumTestCase): self.assertHTMLEqual( fk_dropdown.get_attribute("innerHTML"), f""" - <option value="" selected>---------</option> + <option value="" selected>- Select an option -</option> <option value="{id_value}">{name}</option> """, ) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index bebd51b0df..399c485fea 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -26,6 +26,7 @@ from django.contrib.contenttypes.models import ContentType from django.core import mail from django.core.checks import Error from django.core.files import temp as tempfile +from django.db.models.utils import get_blank_choice_label from django.forms.utils import ErrorList from django.template.response import TemplateResponse from django.test import ( @@ -6929,7 +6930,7 @@ class SeleniumTests(AdminSeleniumTestCase): self.selenium.switch_to.window(self.selenium.window_handles[0]) select = Select(self.selenium.find_element(By.ID, "id_parent")) self.assertEqual(ParentWithUUIDPK.objects.count(), 0) - self.assertEqual(select.first_selected_option.text, "---------") + self.assertEqual(select.first_selected_option.text, get_blank_choice_label()) self.assertEqual(select.first_selected_option.get_attribute("value"), "") def test_inline_with_popup_cancel_delete(self): @@ -7179,7 +7180,7 @@ class SeleniumTests(AdminSeleniumTestCase): self.assertHTMLEqual( _get_HTML_inside_element_by_id(born_country_select_id), f""" - <option value="" selected="">---------</option> + <option value="" selected="">- Select an option -</option> <option value="{argentina.pk}" selected="">Argentina</option> """, ) @@ -7198,7 +7199,7 @@ class SeleniumTests(AdminSeleniumTestCase): # limit_choices_to. self.assertHTMLEqual( _get_HTML_inside_element_by_id(favorite_country_to_vacation_select_id), - '<option value="" selected="">---------</option>', + '<option value="" selected="">- Select an option -</option>', ) # Add new Country from the living_country select. @@ -7218,7 +7219,7 @@ class SeleniumTests(AdminSeleniumTestCase): self.assertHTMLEqual( _get_HTML_inside_element_by_id(born_country_select_id), f""" - <option value="" selected="">---------</option> + <option value="" selected="">- Select an option -</option> <option value="{argentina.pk}" selected="">Argentina</option> <option value="{spain.pk}">Spain</option> """, @@ -7240,7 +7241,7 @@ class SeleniumTests(AdminSeleniumTestCase): # limit_choices_to. self.assertHTMLEqual( _get_HTML_inside_element_by_id(favorite_country_to_vacation_select_id), - '<option value="" selected="">---------</option>', + '<option value="" selected="">- Select an option -</option>', ) # Edit second Country created from living_country select. @@ -7261,7 +7262,7 @@ class SeleniumTests(AdminSeleniumTestCase): self.assertHTMLEqual( _get_HTML_inside_element_by_id(born_country_select_id), f""" - <option value="" selected="">---------</option> + <option value="" selected="">- Select an option -</option> <option value="{argentina.pk}" selected="">Argentina</option> <option value="{italy.pk}">Italy</option> """, @@ -7281,7 +7282,7 @@ class SeleniumTests(AdminSeleniumTestCase): # favorite_country_to_vacation field has no options. self.assertHTMLEqual( _get_HTML_inside_element_by_id(favorite_country_to_vacation_select_id), - '<option value="" selected="">---------</option>', + '<option value="" selected="">- Select an option -</option>', ) # Add a new Asian country. diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index 5a8b29b83a..b1db728f67 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -1975,8 +1975,8 @@ class RelatedFieldWidgetSeleniumTests(AdminWidgetSeleniumTestCase): # Chrome and Safari don't update related object links when selecting # the same option as previously submitted. As a consequence, the - # "pencil" and "eye" buttons remain disable, so select "---------" - # first. + # "pencil" and "eye" buttons remain disable, so select + # "- Select an option -" first. select = Select(self.selenium.find_element(By.ID, "id_user")) select.select_by_index(0) select.select_by_value("newuser") diff --git a/tests/forms_tests/field_tests/test_typedchoicefield.py b/tests/forms_tests/field_tests/test_typedchoicefield.py index 3537623272..fe5cbe12ee 100644 --- a/tests/forms_tests/field_tests/test_typedchoicefield.py +++ b/tests/forms_tests/field_tests/test_typedchoicefield.py @@ -1,6 +1,7 @@ import decimal from django.core.exceptions import ValidationError +from django.db.models.utils import get_blank_choice_label from django.forms import TypedChoiceField from django.test import SimpleTestCase @@ -62,7 +63,11 @@ class TypedChoiceFieldTest(SimpleTestCase): self.assertFalse(f.has_changed("1", "1")) f = TypedChoiceField( - choices=[("", "---------"), ("a", "a"), ("b", "b")], + choices=[ + ("", get_blank_choice_label()), + ("a", "a"), + ("b", "b"), + ], coerce=str, required=False, initial=None, diff --git a/tests/forms_tests/locale/de/LC_MESSAGES/django.mo b/tests/forms_tests/locale/de/LC_MESSAGES/django.mo Binary files differnew file mode 100644 index 0000000000..f2695ea426 --- /dev/null +++ b/tests/forms_tests/locale/de/LC_MESSAGES/django.mo diff --git a/tests/forms_tests/locale/de/LC_MESSAGES/django.po b/tests/forms_tests/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000000..e118e28d98 --- /dev/null +++ b/tests/forms_tests/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,14 @@ +# This file is distributed under the same license as the Django package. +# +msgid "" +msgstr "" +"Project-Id-Version: django\n" +"Report-Msgid-Bugs-To: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "- Select an option -" +msgstr "- Wähle eine Option -" diff --git a/tests/forms_tests/locale/de/__init__.py b/tests/forms_tests/locale/de/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/forms_tests/locale/de/__init__.py diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py index 14fbc19a59..2c1ec78650 100644 --- a/tests/forms_tests/tests/test_forms.py +++ b/tests/forms_tests/tests/test_forms.py @@ -1,11 +1,14 @@ import copy import datetime import json +import textwrap import uuid +from django.conf import USE_BLANK_CHOICE_DASH_DEPRECATED_MSG from django.core.exceptions import NON_FIELD_ERRORS from django.core.files.uploadedfile import SimpleUploadedFile from django.core.validators import MaxValueValidator, RegexValidator +from django.db.models.utils import get_blank_choice_label from django.forms import ( BooleanField, BoundField, @@ -42,9 +45,10 @@ from django.forms.renderers import DjangoTemplates, get_default_renderer from django.forms.utils import ErrorDict, ErrorList from django.http import QueryDict from django.template import Context, Template -from django.test import SimpleTestCase +from django.test import SimpleTestCase, ignore_warnings from django.test.utils import override_settings from django.utils.datastructures import MultiValueDict +from django.utils.deprecation import RemovedInDjango70Warning from django.utils.safestring import mark_safe from . import jinja2_tests @@ -797,6 +801,37 @@ aria-describedby="id_birthday_error"> </select>""", ) + # RemovedInDjango70Warning + @ignore_warnings(category=RemovedInDjango70Warning) + @override_settings(USE_BLANK_CHOICE_DASH=True) + def test_blank_choice_dash(self): + class SomeForm(Form): + somechoices = ChoiceField( + choices=( + ("0", get_blank_choice_label()), + ("1", "Test 1"), + ("2", "Test 2"), + ) + ) + + f = SomeForm() + + self.assertHTMLEqual( + f.as_p(), + textwrap.dedent(""" + <p> + <label for="id_somechoices">Somechoices:</label> + <select name="somechoices" id="id_somechoices"> + <option value="0">---------</option> + <option value="1">Test 1</option> + <option value="2">Test 2</option></select> + </p>"""), + ) + + self.assertWarnsMessage( + RemovedInDjango70Warning, USE_BLANK_CHOICE_DASH_DEPRECATED_MSG + ) + def test_forms_with_radio(self): # Add widget=RadioSelect to use that widget with a ChoiceField. f = FrameworkForm(auto_id=False) diff --git a/tests/forms_tests/tests/test_i18n.py b/tests/forms_tests/tests/test_i18n.py index 5b8d92c0f6..fe6a67dd16 100644 --- a/tests/forms_tests/tests/test_i18n.py +++ b/tests/forms_tests/tests/test_i18n.py @@ -1,3 +1,6 @@ +import textwrap + +from django.db.models.utils import get_blank_choice_label from django.forms import ( CharField, ChoiceField, @@ -122,6 +125,41 @@ class FormsI18nTests(SimpleTestCase): CopyForm() + def test_blank_choice_label(self): + class SomeForm(Form): + somechoices = ChoiceField( + choices=( + ("0", get_blank_choice_label()), + ("1", "Test 1"), + ("2", "Test 2"), + ) + ) + + f = SomeForm() + + self.assertHTMLEqual( + f.as_p(), + textwrap.dedent(""" + <p> + <label for="id_somechoices">Somechoices:</label> + <select name="somechoices" id="id_somechoices"> + <option value="0">- Select an option -</option> + <option value="1">Test 1</option> + <option value="2">Test 2</option></select> + </p>"""), + ) + + with translation.override("de"): + self.assertHTMLEqual( + f.as_p(), + textwrap.dedent("""<p><label for="id_somechoices">Somechoices:</label> + <select name="somechoices" id="id_somechoices"> + <option value="0">- Wähle eine Option -</option> + <option value="1">Test 1</option> + <option value="2">Test 2</option></select> + </p>"""), + ) + @jinja2_tests class Jinja2FormsI18nTests(FormsI18nTests): diff --git a/tests/forms_tests/widget_tests/test_radioselect.py b/tests/forms_tests/widget_tests/test_radioselect.py index be336151ef..a4d4919009 100644 --- a/tests/forms_tests/widget_tests/test_radioselect.py +++ b/tests/forms_tests/widget_tests/test_radioselect.py @@ -1,12 +1,13 @@ import datetime +from django.db.models.utils import get_blank_choice_label from django.forms import ChoiceField, Form, MultiWidget, RadioSelect, TextInput from django.test import override_settings from django.utils.safestring import mark_safe from .test_choicewidget import ChoiceWidgetTest -BLANK_CHOICE_DASH = (("", "------"),) +BLANK_CHOICE = (("", get_blank_choice_label()),) class RadioSelectTest(ChoiceWidgetTest): @@ -16,7 +17,9 @@ class RadioSelectTest(ChoiceWidgetTest): html = """ <div> <div> - <label><input type="radio" name="beatle" value="">------</label> + <label><input type="radio" name="beatle" value=""> + - Select an option - + </label> </div> <div> <label><input checked type="radio" name="beatle" value="J">John</label> @@ -32,7 +35,7 @@ class RadioSelectTest(ChoiceWidgetTest): </div> </div> """ - beatles_with_blank = BLANK_CHOICE_DASH + self.beatles + beatles_with_blank = BLANK_CHOICE + self.beatles for choices in (beatles_with_blank, dict(beatles_with_blank)): with self.subTest(choices): self.check_html(self.widget(choices=choices), "beatle", "J", html=html) @@ -83,11 +86,13 @@ class RadioSelectTest(ChoiceWidgetTest): """ If value is None, none of the options are selected. """ - choices = BLANK_CHOICE_DASH + self.beatles + choices = BLANK_CHOICE + self.beatles html = """ <div> <div> - <label><input checked type="radio" name="beatle" value="">------</label> + <label><input checked type="radio" name="beatle" value=""> + - Select an option - + </label> </div> <div> <label><input type="radio" name="beatle" value="J">John</label> @@ -463,11 +468,11 @@ class RadioSelectTest(ChoiceWidgetTest): def test_render_as_subwidget(self): """A RadioSelect as a subwidget of MultiWidget.""" - choices = BLANK_CHOICE_DASH + self.beatles + choices = BLANK_CHOICE + self.beatles html = """ <div> <div><label> - <input type="radio" name="beatle_0" value="">------</label> + <input type="radio" name="beatle_0" value="">- Select an option -</label> </div> <div><label> <input checked type="radio" name="beatle_0" value="J">John</label> diff --git a/tests/model_fields/test_booleanfield.py b/tests/model_fields/test_booleanfield.py index 30eb009eb7..d0ed6e86cc 100644 --- a/tests/model_fields/test_booleanfield.py +++ b/tests/model_fields/test_booleanfield.py @@ -1,6 +1,7 @@ from django import forms from django.core.exceptions import ValidationError from django.db import IntegrityError, models, transaction +from django.db.models.utils import get_blank_choice_label from django.test import SimpleTestCase, TestCase from .models import BooleanModel, FksToBooleans, NullBooleanModel @@ -48,7 +49,9 @@ class BooleanFieldTests(TestCase): """ choices = [(1, "Si"), (2, "No")] f = models.BooleanField(choices=choices) - self.assertEqual(f.formfield().choices, [("", "---------")] + choices) + self.assertEqual( + f.formfield().choices, [("", get_blank_choice_label())] + choices + ) def test_nullbooleanfield_formfield(self): f = models.BooleanField(null=True) diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index b27c07a92f..fa7436343a 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -391,7 +391,10 @@ class GetChoicesTests(SimpleTestCase): def test_lazy_strings_not_evaluated(self): lazy_func = lazy(lambda x: 0 / 0, int) # raises ZeroDivisionError if evaluated. f = models.CharField(choices=[(lazy_func("group"), [("a", "A"), ("b", "B")])]) - self.assertEqual(f.get_choices(include_blank=True)[0], ("", "---------")) + self.assertEqual( + f.get_choices(include_blank=True)[0], + ("", models.utils.get_blank_choice_label()), + ) class GetChoicesOrderingTests(TestCase): diff --git a/tests/model_forms/test_modelchoicefield.py b/tests/model_forms/test_modelchoicefield.py index 7f66b5b078..40c625da7c 100644 --- a/tests/model_forms/test_modelchoicefield.py +++ b/tests/model_forms/test_modelchoicefield.py @@ -2,6 +2,7 @@ import datetime from django import forms from django.core.exceptions import ValidationError +from django.db.models.utils import get_blank_choice_label from django.forms.models import ModelChoiceIterator, ModelChoiceIteratorValue from django.forms.widgets import CheckboxSelectMultiple from django.template import Context, Template @@ -24,7 +25,7 @@ class ModelChoiceFieldTests(TestCase): self.assertEqual( list(f.choices), [ - ("", "---------"), + ("", get_blank_choice_label()), (self.c1.pk, "Entertainment"), (self.c2.pk, "A test"), (self.c3.pk, "Third"), @@ -102,7 +103,7 @@ class ModelChoiceFieldTests(TestCase): self.assertEqual( list(f.choices), [ - ("", "---------"), + ("", get_blank_choice_label()), (self.c1.pk, "Entertainment"), (self.c2.pk, "A test"), ], @@ -118,7 +119,7 @@ class ModelChoiceFieldTests(TestCase): self.assertEqual( list(gen_two), [ - ("", "---------"), + ("", get_blank_choice_label()), (self.c1.pk, "Entertainment"), (self.c2.pk, "A test"), ], @@ -130,7 +131,7 @@ class ModelChoiceFieldTests(TestCase): self.assertEqual( list(f.choices), [ - ("", "---------"), + ("", get_blank_choice_label()), (self.c1.pk, "category Entertainment"), (self.c2.pk, "category A test"), (self.c3.pk, "category Third"), @@ -143,7 +144,7 @@ class ModelChoiceFieldTests(TestCase): self.assertEqual( list(f.choices), [ - ("", "---------"), + ("", get_blank_choice_label()), (self.c1.pk, "Entertainment"), (self.c2.pk, "A test"), (self.c3.pk, "Third"), @@ -154,7 +155,7 @@ class ModelChoiceFieldTests(TestCase): self.assertEqual( list(f.choices), [ - ("", "---------"), + ("", get_blank_choice_label()), (self.c1.pk, "Entertainment"), (self.c2.pk, "A test"), (self.c3.pk, "Third"), @@ -174,6 +175,7 @@ class ModelChoiceFieldTests(TestCase): self.assertIs(bool(f.choices), True) def test_choices_radio_blank(self): + blank_choice = [("", get_blank_choice_label())] choices = [ (self.c1.pk, "Entertainment"), (self.c2.pk, "A test"), @@ -190,7 +192,7 @@ class ModelChoiceFieldTests(TestCase): ) self.assertEqual( list(f.choices), - [("", "---------")] + choices if blank else choices, + (blank_choice + choices if blank else choices), ) def test_deepcopies_widget(self): @@ -424,7 +426,7 @@ class ModelChoiceFieldTests(TestCase): self.assertCountEqual( list(f.choices), [ - ("", "---------"), + ("", get_blank_choice_label()), (self.c1.pk, "Entertainment"), (self.c2.pk, "A test"), (self.c3.pk, "Third"), diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index 3d4eb06cf4..466b7dc57b 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -14,6 +14,7 @@ from django.core.exceptions import ( from django.core.files.uploadedfile import SimpleUploadedFile from django.db import connection, models from django.db.models.query import EmptyQuerySet +from django.db.models.utils import get_blank_choice_label from django.forms.models import ( ModelFormMetaclass, construct_instance, @@ -343,7 +344,7 @@ class ModelFormBaseTest(TestCase): self.assertEqual( list(form.fields["author"].choices), [ - ("", "---------"), + ("", get_blank_choice_label()), (writer.pk, "Joe Doe"), ], ) @@ -1531,7 +1532,7 @@ class ModelFormBasicTests(TestCase): <li>Slug: <input type="text" name="slug" maxlength="50" required></li> <li>Pub date: <input type="text" name="pub_date" required></li> <li>Writer: <select name="writer" required> - <option value="" selected>---------</option> + <option value="" selected>- Select an option -</option> <option value="%s">Bob Woodward</option> <option value="%s">Mike Royko</option> </select></li> @@ -1543,7 +1544,7 @@ class ModelFormBasicTests(TestCase): <option value="%s">Third test</option> </select></li> <li>Status: <select name="status"> - <option value="" selected>---------</option> + <option value="" selected>- Select an option -</option> <option value="1">Draft</option> <option value="2">Pending</option> <option value="3">Live</option> @@ -1585,7 +1586,7 @@ class ModelFormBasicTests(TestCase): <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" required></li> <li>Writer: <select name="writer" required> - <option value="">---------</option> + <option value="">- Select an option -</option> <option value="%s">Bob Woodward</option> <option value="%s" selected>Mike Royko</option> </select></li> @@ -1597,7 +1598,7 @@ class ModelFormBasicTests(TestCase): <option value="%s">Third test</option> </select></li> <li>Status: <select name="status"> - <option value="" selected>---------</option> + <option value="" selected>- Select an option -</option> <option value="1">Draft</option> <option value="2">Pending</option> <option value="3">Live</option> @@ -1731,7 +1732,7 @@ class ModelFormBasicTests(TestCase): </div> <div>Writer: <select name="writer" required> - <option value="" selected>---------</option> + <option value="" selected>- Select an option -</option> <option value="%s">Bob Woodward</option> <option value="%s">Mike Royko</option> </select> @@ -1748,7 +1749,7 @@ class ModelFormBasicTests(TestCase): </div> <div>Status: <select name="status"> - <option value="" selected>---------</option> + <option value="" selected>- Select an option -</option> <option value="1">Draft</option><option value="2">Pending</option> <option value="3">Live</option> </select> @@ -1781,7 +1782,7 @@ class ModelFormBasicTests(TestCase): <li>Pub date: <input type="text" name="pub_date" value="1988-01-04" required></li> <li>Writer: <select name="writer" required> - <option value="">---------</option> + <option value="">- Select an option -</option> <option value="%s">Bob Woodward</option> <option value="%s" selected>Mike Royko</option> </select></li> @@ -1793,7 +1794,7 @@ class ModelFormBasicTests(TestCase): <option value="%s">Third test</option> </select></li> <li>Status: <select name="status"> - <option value="" selected>---------</option> + <option value="" selected>- Select an option -</option> <option value="1">Draft</option> <option value="2">Pending</option> <option value="3">Live</option> @@ -1957,7 +1958,7 @@ class ModelFormBasicTests(TestCase): '<li>Slug: <input type="text" name="slug" maxlength="50" required></li>' '<li>Pub date: <input type="text" name="pub_date" required></li>' '<li>Writer: <select name="writer" required>' - '<option value="" selected>---------</option>' + '<option value="" selected>- Select an option -</option>' '<option value="%s">Bob Woodward</option>' '<option value="%s">Mike Royko</option>' "</select></li>" @@ -1969,7 +1970,7 @@ class ModelFormBasicTests(TestCase): '<option value="%s">Third test</option>' "</select> </li>" '<li>Status: <select name="status">' - '<option value="" selected>---------</option>' + '<option value="" selected>- Select an option -</option>' '<option value="1">Draft</option>' '<option value="2">Pending</option>' '<option value="3">Live</option>' @@ -1986,7 +1987,7 @@ class ModelFormBasicTests(TestCase): '<li>Slug: <input type="text" name="slug" maxlength="50" required></li>' '<li>Pub date: <input type="text" name="pub_date" required></li>' '<li>Writer: <select name="writer" required>' - '<option value="" selected>---------</option>' + '<option value="" selected>- Select an option -</option>' '<option value="%s">Bob Woodward</option>' '<option value="%s">Carl Bernstein</option>' '<option value="%s">Mike Royko</option>' @@ -2000,7 +2001,7 @@ class ModelFormBasicTests(TestCase): '<option value="%s">Fourth</option>' "</select></li>" '<li>Status: <select name="status">' - '<option value="" selected>---------</option>' + '<option value="" selected>- Select an option -</option>' '<option value="1">Draft</option>' '<option value="2">Pending</option>' '<option value="3">Live</option>' @@ -2044,7 +2045,7 @@ class ModelFormBasicTests(TestCase): self.assertEqual(call_count, 0) self.assertEqual( form.fields["animal"].choices, - models.BLANK_CHOICE_DASH + [("LION", "Lion"), ("ZEBRA", "Zebra")], + [("", get_blank_choice_label())] + [("LION", "Lion"), ("ZEBRA", "Zebra")], ) self.assertEqual(call_count, 1) @@ -2410,7 +2411,7 @@ class ModelOneToOneFieldTests(TestCase): """ <p><label for="id_writer">Writer:</label> <select name="writer" id="id_writer" required> - <option value="" selected>---------</option> + <option value="" selected>- Select an option -</option> <option value="%s">Bob Woodward</option> <option value="%s">Mike Royko</option> </select></p> @@ -2437,7 +2438,7 @@ class ModelOneToOneFieldTests(TestCase): """ <p><label for="id_writer">Writer:</label> <select name="writer" id="id_writer" required> - <option value="">---------</option> + <option value="">- Select an option -</option> <option value="%s" selected>Bob Woodward</option> <option value="%s">Mike Royko</option> </select></p> @@ -2727,7 +2728,8 @@ class FileAndImageFieldTests(TestCase): form = FPForm() self.assertEqual( - [name for _, name in form["path"].field.choices], ["---------", "models.py"] + [name for _, name in form["path"].field.choices], + [get_blank_choice_label(), "models.py"], ) @skipUnless(test_images, "Pillow not installed") @@ -3027,7 +3029,7 @@ class OtherModelFormTests(TestCase): self.assertEqual( tuple(field.choices), ( - ("", "---------"), + ("", get_blank_choice_label()), (multicolor_item.pk, "blue, red"), (red_item.pk, "red"), ), @@ -3041,14 +3043,19 @@ class OtherModelFormTests(TestCase): field = forms.ModelChoiceField(Inventory.objects.all(), to_field_name="barcode") self.assertEqual( tuple(field.choices), - (("", "---------"), (86, "Apple"), (87, "Core"), (22, "Pear")), + ( + ("", get_blank_choice_label()), + (86, "Apple"), + (87, "Core"), + (22, "Pear"), + ), ) form = InventoryForm(instance=core) self.assertHTMLEqual( str(form["parent"]), """<select name="parent" id="id_parent"> -<option value="">---------</option> +<option value="">- Select an option -</option> <option value="86" selected>Apple</option> <option value="87">Core</option> <option value="22">Pear</option> diff --git a/tests/model_formsets/tests.py b/tests/model_formsets/tests.py index 9436642573..75e4a42163 100644 --- a/tests/model_formsets/tests.py +++ b/tests/model_formsets/tests.py @@ -1267,9 +1267,9 @@ class ModelFormsetTest(TestCase): formset.forms[0].as_p(), '<p><label for="id_form-0-owner">Owner:</label>' '<select name="form-0-owner" id="id_form-0-owner">' - '<option value="" selected>---------</option>' - '<option value="%s">Joe Perry at Giordanos</option>' - '<option value="%s">Jack Berry at Giordanos</option>' + '<option value="" selected>- Select an option -</option>' + '<option value="%d">Joe Perry at Giordanos</option>' + '<option value="%d">Jack Berry at Giordanos</option>' "</select></p>" '<p><label for="id_form-0-age">Age:</label>' '<input type="number" name="form-0-age" id="id_form-0-age" min="0"></p>' diff --git a/tests/modeladmin/tests.py b/tests/modeladmin/tests.py index 74bbc83efb..5ca7999bf0 100644 --- a/tests/modeladmin/tests.py +++ b/tests/modeladmin/tests.py @@ -18,6 +18,7 @@ from django.contrib.admin.widgets import ( ) from django.contrib.auth.models import User from django.db import models +from django.db.models.utils import get_blank_choice_label from django.forms.widgets import Select from django.test import RequestFactory, SimpleTestCase, TestCase from django.test.utils import isolate_apps @@ -636,7 +637,7 @@ class ModelAdminTests(TestCase): '<div class="related-widget-wrapper" data-model-ref="band">' '<select data-context="available-source" ' 'name="main_band" id="id_main_band" required>' - '<option value="" selected>---------</option>' + '<option value="" selected>- Select an option -</option>' '<option value="%s">The Beatles</option>' '<option value="%s">The Doors</option>' "</select></div>" % (band2.id, self.band.id), @@ -660,8 +661,8 @@ class ModelAdminTests(TestCase): '<div class="related-widget-wrapper" data-model-ref="band">' '<select data-context="available-source" ' 'name="main_band" id="id_main_band" required>' - '<option value="" selected>---------</option>' - '<option value="%s">The Doors</option>' + '<option value="" selected>- Select an option -</option>' + '<option value="%d">The Doors</option>' "</select></div>" % self.band.id, ) @@ -708,30 +709,31 @@ class ModelAdminTests(TestCase): # ForeignKey widgets in the admin are wrapped with # RelatedFieldWidgetWrapper so they need to be handled properly when # type checking. For Select fields, all of the choices lists have a - # first entry of dashes. + # first entry of a translatable blank choice label. + blank_option = ("", get_blank_choice_label()) cma = ModelAdmin(Concert, self.site) cmafa = cma.get_form(request) self.assertEqual(type(cmafa.base_fields["main_band"].widget.widget), Select) self.assertEqual( list(cmafa.base_fields["main_band"].widget.choices), - [("", "---------"), (self.band.id, "The Doors")], + [blank_option, (self.band.id, "The Doors")], ) self.assertEqual(type(cmafa.base_fields["opening_band"].widget.widget), Select) self.assertEqual( list(cmafa.base_fields["opening_band"].widget.choices), - [("", "---------"), (self.band.id, "The Doors")], + [blank_option, (self.band.id, "The Doors")], ) self.assertEqual(type(cmafa.base_fields["day"].widget), Select) self.assertEqual( list(cmafa.base_fields["day"].widget.choices), - [("", "---------"), (1, "Fri"), (2, "Sat")], + [blank_option, (1, "Fri"), (2, "Sat")], ) self.assertEqual(type(cmafa.base_fields["transport"].widget), Select) self.assertEqual( list(cmafa.base_fields["transport"].widget.choices), - [("", "---------"), (1, "Plane"), (2, "Train"), (3, "Bus")], + [blank_option, (1, "Plane"), (2, "Train"), (3, "Bus")], ) def test_foreign_key_as_radio_field(self): @@ -849,7 +851,7 @@ class ModelAdminTests(TestCase): 'class="related-widget-wrapper" data-model-ref="band"><select ' 'name="main_band" data-context="available-source" required ' 'id="id_main_band" data-custom-widget="true" multiple>' - '<option value="">---------</option>' + '<option value="">- Select an option -</option>' f'<option value="{self.band.pk}">The Doors</option>' "</select></div></div>" ) |
