diff --git a/django/conf/__init__.py b/django/conf/__init__.py index 6b5f044e34..5568d7cc83 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -16,12 +16,18 @@ from pathlib import Path import django from django.conf import global_settings from django.core.exceptions import ImproperlyConfigured +from django.utils.deprecation import RemovedInDjango60Warning from django.utils.functional import LazyObject, empty ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" DEFAULT_STORAGE_ALIAS = "default" STATICFILES_STORAGE_ALIAS = "staticfiles" +# RemovedInDjango60Warning. +FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG = ( + "The FORMS_URLFIELD_ASSUME_HTTPS transitional setting is deprecated." +) + class SettingsReference(str): """ @@ -180,6 +186,12 @@ class Settings: setattr(self, setting, setting_value) self._explicit_settings.add(setting) + if self.is_overridden("FORMS_URLFIELD_ASSUME_HTTPS"): + warnings.warn( + FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG, + RemovedInDjango60Warning, + ) + if hasattr(time, "tzset") and self.TIME_ZONE: # When we can, attempt to validate the timezone. If we can't find # this file, no check happens and it's harmless. @@ -224,6 +236,11 @@ class UserSettingsHolder: def __setattr__(self, name, value): self._deleted.discard(name) + if name == "FORMS_URLFIELD_ASSUME_HTTPS": + warnings.warn( + FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG, + RemovedInDjango60Warning, + ) super().__setattr__(name, value) def __delattr__(self, name): diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 6b91c6a716..8e1d2ace09 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -216,6 +216,11 @@ TEMPLATES = [] # Default form rendering class. FORM_RENDERER = "django.forms.renderers.DjangoTemplates" +# RemovedInDjango60Warning: It's a transitional setting helpful in early +# adoption of "https" as the new default value of forms.URLField.assume_scheme. +# Set to True to assume "https" during the Django 5.x release cycle. +FORMS_URLFIELD_ASSUME_HTTPS = False + # Default email address to use for various automated correspondence from # the site managers. DEFAULT_FROM_EMAIL = "webmaster@localhost" diff --git a/django/forms/fields.py b/django/forms/fields.py index d1ba8af654..62d68985c0 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -15,6 +15,7 @@ from decimal import Decimal, DecimalException from io import BytesIO from urllib.parse import urlsplit, urlunsplit +from django.conf import settings from django.core import validators from django.core.exceptions import ValidationError from django.forms.boundfield import BoundField @@ -762,14 +763,19 @@ class URLField(CharField): def __init__(self, *, assume_scheme=None, **kwargs): if assume_scheme is None: - warnings.warn( - "The default scheme will be changed from 'http' to 'https' in Django " - "6.0. Pass the forms.URLField.assume_scheme argument to silence this " - "warning.", - RemovedInDjango60Warning, - stacklevel=2, - ) - assume_scheme = "http" + if settings.FORMS_URLFIELD_ASSUME_HTTPS: + assume_scheme = "https" + else: + warnings.warn( + "The default scheme will be changed from 'http' to 'https' in " + "Django 6.0. Pass the forms.URLField.assume_scheme argument to " + "silence this warning, or set the FORMS_URLFIELD_ASSUME_HTTPS " + "transitional setting to True to opt into using 'https' as the new " + "default scheme.", + RemovedInDjango60Warning, + stacklevel=2, + ) + assume_scheme = "http" # RemovedInDjango60Warning: When the deprecation ends, replace with: # self.assume_scheme = assume_scheme or "https" self.assume_scheme = assume_scheme diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index dd6712e936..edda364b73 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -53,6 +53,8 @@ details on these changes. * ``get_prefetcher()`` and ``prefetch_related_objects()`` will no longer fallback to ``get_prefetch_queryset()``. +* The ``FORMS_URLFIELD_ASSUME_HTTPS`` transitional setting will be removed. + See the :ref:`Django 5.1 release notes ` for more details on these changes. diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 0f32df0ebb..a3e0bf1aba 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -1155,7 +1155,9 @@ For each field, we describe the default widget used if you don't specify .. deprecated:: 5.0 The default value for ``assume_scheme`` will change from ``"http"`` to - ``"https"`` in Django 6.0. + ``"https"`` in Django 6.0. Set :setting:`FORMS_URLFIELD_ASSUME_HTTPS` + transitional setting to ``True`` to opt into using ``"https"`` during + the Django 5.x release cycle. ``UUIDField`` ------------- diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 67f6482712..5d3e893cc7 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1675,6 +1675,20 @@ renderers are: * ``'``:class:`django.forms.renderers.Jinja2`\ ``'`` * ``'``:class:`django.forms.renderers.TemplatesSetting`\ ``'`` +.. setting:: FORMS_URLFIELD_ASSUME_HTTPS + +``FORMS_URLFIELD_ASSUME_HTTPS`` +------------------------------- + +.. versionadded:: 5.0 +.. deprecated:: 5.0 + +Default: ``False`` + +Set this transitional setting to ``True`` to opt into using ``"https"`` as the +new default value of :attr:`URLField.assume_scheme +` during the Django 5.x release cycle. + .. setting:: FORMAT_MODULE_PATH ``FORMAT_MODULE_PATH`` @@ -3635,6 +3649,7 @@ File uploads Forms ----- * :setting:`FORM_RENDERER` +* :setting:`FORMS_URLFIELD_ASSUME_HTTPS` Globalization (``i18n``/``l10n``) --------------------------------- diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt index b74a0f00c7..ebddec38e6 100644 --- a/docs/releases/5.0.txt +++ b/docs/releases/5.0.txt @@ -612,7 +612,11 @@ Miscellaneous * The ``ForeignObject.get_reverse_joining_columns()`` method is deprecated. * The default scheme for ``forms.URLField`` will change from ``"http"`` to - ``"https"`` in Django 6.0. + ``"https"`` in Django 6.0. Set :setting:`FORMS_URLFIELD_ASSUME_HTTPS` + transitional setting to ``True`` to opt into assuming ``"https"`` during the + Django 5.x release cycle. + +* ``FORMS_URLFIELD_ASSUME_HTTPS`` transitional setting is deprecated. * Support for calling ``format_html()`` without passing args or kwargs will be removed. diff --git a/tests/forms_tests/field_tests/test_urlfield.py b/tests/forms_tests/field_tests/test_urlfield.py index 2cd1a82694..8ba7842064 100644 --- a/tests/forms_tests/field_tests/test_urlfield.py +++ b/tests/forms_tests/field_tests/test_urlfield.py @@ -1,3 +1,7 @@ +import sys +from types import ModuleType + +from django.conf import FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG, Settings, settings from django.core.exceptions import ValidationError from django.forms import URLField from django.test import SimpleTestCase, ignore_warnings @@ -155,8 +159,41 @@ class URLFieldAssumeSchemeDeprecationTest(FormFieldAssertionsMixin, SimpleTestCa def test_urlfield_raises_warning(self): msg = ( "The default scheme will be changed from 'http' to 'https' in Django 6.0. " - "Pass the forms.URLField.assume_scheme argument to silence this warning." + "Pass the forms.URLField.assume_scheme argument to silence this warning, " + "or set the FORMS_URLFIELD_ASSUME_HTTPS transitional setting to True to " + "opt into using 'https' as the new default scheme." ) with self.assertWarnsMessage(RemovedInDjango60Warning, msg): f = URLField() self.assertEqual(f.clean("example.com"), "http://example.com") + + @ignore_warnings(category=RemovedInDjango60Warning) + def test_urlfield_forms_urlfield_assume_https(self): + with self.settings(FORMS_URLFIELD_ASSUME_HTTPS=True): + f = URLField() + self.assertEqual(f.clean("example.com"), "https://example.com") + f = URLField(assume_scheme="http") + self.assertEqual(f.clean("example.com"), "http://example.com") + + def test_override_forms_urlfield_assume_https_setting_warning(self): + msg = FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG + with self.assertRaisesMessage(RemovedInDjango60Warning, msg): + # Changing FORMS_URLFIELD_ASSUME_HTTPS via self.settings() raises a + # deprecation warning. + with self.settings(FORMS_URLFIELD_ASSUME_HTTPS=True): + pass + + def test_settings_init_forms_urlfield_assume_https_warning(self): + settings_module = ModuleType("fake_settings_module") + settings_module.FORMS_URLFIELD_ASSUME_HTTPS = True + sys.modules["fake_settings_module"] = settings_module + msg = FORMS_URLFIELD_ASSUME_HTTPS_DEPRECATED_MSG + try: + with self.assertRaisesMessage(RemovedInDjango60Warning, msg): + Settings("fake_settings_module") + finally: + del sys.modules["fake_settings_module"] + + def test_access_forms_urlfield_assume_https(self): + # Warning is not raised on access. + self.assertEqual(settings.FORMS_URLFIELD_ASSUME_HTTPS, False) diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index f706271106..3f927cb053 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -2930,7 +2930,8 @@ class ModelOtherFieldTests(SimpleTestCase): msg = ( "The default scheme will be changed from 'http' to 'https' in Django " "6.0. Pass the forms.URLField.assume_scheme argument to silence this " - "warning." + "warning, or set the FORMS_URLFIELD_ASSUME_HTTPS transitional setting to " + "True to opt into using 'https' as the new default scheme." ) with self.assertWarnsMessage(RemovedInDjango60Warning, msg): @@ -2939,6 +2940,18 @@ class ModelOtherFieldTests(SimpleTestCase): model = Homepage fields = "__all__" + def test_url_modelform_assume_scheme_early_adopt_https(self): + msg = "The FORMS_URLFIELD_ASSUME_HTTPS transitional setting is deprecated." + with ( + self.assertWarnsMessage(RemovedInDjango60Warning, msg), + self.settings(FORMS_URLFIELD_ASSUME_HTTPS=True), + ): + + class HomepageForm(forms.ModelForm): + class Meta: + model = Homepage + fields = "__all__" + def test_modelform_non_editable_field(self): """ When explicitly including a non-editable field in a ModelForm, the