diff --git a/django/urls/converters.py b/django/urls/converters.py index 965282350805..9b4443058047 100644 --- a/django/urls/converters.py +++ b/django/urls/converters.py @@ -1,5 +1,8 @@ import functools import uuid +import warnings + +from django.utils.deprecation import RemovedInDjango60Warning class IntConverter: @@ -53,6 +56,15 @@ class PathConverter(StringConverter): def register_converter(converter, type_name): + if type_name in REGISTERED_CONVERTERS or type_name in DEFAULT_CONVERTERS: + # RemovedInDjango60Warning: when the deprecation ends, replace with + # raise ValueError(f"Converter {type_name} is already registered.") + warnings.warn( + f"Converter {type_name!r} is already registered. Support for overriding " + "registered converters is deprecated and will be removed in Django 6.0.", + RemovedInDjango60Warning, + stacklevel=2, + ) REGISTERED_CONVERTERS[type_name] = converter() get_converters.cache_clear() diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index e91ac062cb90..807207586482 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -74,6 +74,9 @@ details on these changes. * The setter for ``django.contrib.gis.gdal.OGRGeometry.coord_dim`` will be removed. +* ``django.urls.register_converter()`` will no longer allow overriding existing + converters. + .. _deprecation-removed-in-5.1: 5.1 diff --git a/docs/ref/urls.txt b/docs/ref/urls.txt index e8d51eeda22b..2ef873d34863 100644 --- a/docs/ref/urls.txt +++ b/docs/ref/urls.txt @@ -120,6 +120,10 @@ The ``converter`` argument is a converter class, and ``type_name`` is the converter name to use in path patterns. See :ref:`registering-custom-path-converters` for an example. +.. deprecated:: 5.1 + + Overriding existing converters is deprecated. + ================================================== ``django.conf.urls`` functions for use in URLconfs ================================================== diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt index a4a7f359c6bc..9a2f2fc6cc13 100644 --- a/docs/releases/5.1.txt +++ b/docs/releases/5.1.txt @@ -419,6 +419,9 @@ Miscellaneous * Setting ``django.contrib.gis.gdal.OGRGeometry.coord_dim`` is deprecated. Use :meth:`~django.contrib.gis.gdal.OGRGeometry.set_3d` instead. +* Overriding existing converters with ``django.urls.register_converter()`` is + deprecated. + Features removed in 5.1 ======================= diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index d8de9635ec2a..8e57732725a8 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -183,6 +183,11 @@ Register custom converter classes in your URLconf using ..., ] +.. deprecated:: 5.1 + + Overriding existing converters with ``django.urls.register_converter()`` is + deprecated. + Using regular expressions ========================= diff --git a/tests/urlpatterns/tests.py b/tests/urlpatterns/tests.py index f8d73fdb4ad3..37109c9a1193 100644 --- a/tests/urlpatterns/tests.py +++ b/tests/urlpatterns/tests.py @@ -4,10 +4,20 @@ from django.core.exceptions import ImproperlyConfigured from django.test import SimpleTestCase from django.test.utils import override_settings -from django.urls import NoReverseMatch, Resolver404, path, re_path, resolve, reverse +from django.urls import ( + NoReverseMatch, + Resolver404, + path, + re_path, + register_converter, + resolve, + reverse, +) +from django.urls.converters import IntConverter +from django.utils.deprecation import RemovedInDjango60Warning from django.views import View -from .converters import DynamicConverter +from .converters import Base64Converter, DynamicConverter from .views import empty_view included_kwargs = {"base": b"hello", "value": b"world"} @@ -193,6 +203,28 @@ def test_invalid_converter(self): with self.assertRaisesMessage(ImproperlyConfigured, msg): path("foo//", empty_view) + def test_warning_override_default_converter(self): + # RemovedInDjango60Warning: when the deprecation ends, replace with + # msg = "Converter 'int' is already registered." + # with self.assertRaisesMessage(ValueError, msg): + msg = ( + "Converter 'int' is already registered. Support for overriding registered " + "converters is deprecated and will be removed in Django 6.0." + ) + with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + register_converter(IntConverter, "int") + + def test_warning_override_converter(self): + # RemovedInDjango60Warning: when the deprecation ends, replace with + # msg = "Converter 'base64' is already registered." + # with self.assertRaisesMessage(ValueError, msg): + msg = ( + "Converter 'base64' is already registered. Support for overriding " + "registered converters is deprecated and will be removed in Django 6.0." + ) + with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + register_converter(Base64Converter, "base64") + def test_invalid_view(self): msg = "view must be a callable or a list/tuple in the case of include()." with self.assertRaisesMessage(TypeError, msg):