diff --git a/django/db/models/enums.py b/django/db/models/enums.py index 94fae4d2f7..20e9e77925 100644 --- a/django/db/models/enums.py +++ b/django/db/models/enums.py @@ -1,13 +1,20 @@ import enum +import warnings from types import DynamicClassAttribute +from django.utils.deprecation import RemovedInDjango60Warning from django.utils.functional import Promise -from django.utils.version import PY312 +from django.utils.version import PY311, PY312 + +if PY311: + from enum import EnumType +else: + from enum import EnumMeta as EnumType __all__ = ["Choices", "IntegerChoices", "TextChoices"] -class ChoicesMeta(enum.EnumMeta): +class ChoicesType(EnumType): """A metaclass for creating a enum choices.""" def __new__(metacls, classname, bases, classdict, **kwds): @@ -59,7 +66,7 @@ class ChoicesMeta(enum.EnumMeta): return [value for value, _ in cls.choices] -class Choices(enum.Enum, metaclass=ChoicesMeta): +class Choices(enum.Enum, metaclass=ChoicesType): """Class for creating enumerated choices.""" @DynamicClassAttribute @@ -93,3 +100,14 @@ class TextChoices(str, Choices): def _generate_next_value_(name, start, count, last_values): return name + + +def __getattr__(name): + if name == "ChoicesMeta": + warnings.warn( + "ChoicesMeta is deprecated in favor of ChoicesType.", + RemovedInDjango60Warning, + stacklevel=2, + ) + return ChoicesType + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/django/utils/choices.py b/django/utils/choices.py index 93a34e403d..a0611d96f1 100644 --- a/django/utils/choices.py +++ b/django/utils/choices.py @@ -20,7 +20,7 @@ class CallableChoiceIterator(BaseChoiceIterator): def normalize_choices(value, *, depth=0): """Normalize choices values consistently for fields and widgets.""" # Avoid circular import when importing django.forms. - from django.db.models.enums import ChoicesMeta + from django.db.models.enums import ChoicesType match value: case BaseChoiceIterator() | Promise() | bytes() | str(): @@ -28,7 +28,7 @@ def normalize_choices(value, *, depth=0): # Because string-like types are iterable, return early to avoid # iterating over them in the guard for the Iterable case below. return value - case ChoicesMeta(): + case ChoicesType(): # Choices enumeration helpers already output in canonical form. return value.choices case Mapping() if depth < 2: diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 98b027cd1a..a400ed97f9 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -42,6 +42,9 @@ details on these changes. * ``BaseDatabaseOperations.field_cast_sql()`` will be removed. +* The ``ChoicesMeta`` alias to ``django.db.models.enums.ChoicesType`` will be + removed. + .. _deprecation-removed-in-5.1: 5.1 diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt index 2e23e40cbf..10ab706645 100644 --- a/docs/releases/5.0.txt +++ b/docs/releases/5.0.txt @@ -685,6 +685,9 @@ Miscellaneous ``BuiltinLookup.process_lhs()`` will no longer call ``field_cast_sql()``. Third-party database backends should implement ``lookup_cast()`` instead. +* The ``django.db.models.enums.ChoicesMeta`` metaclass is renamed to + ``ChoicesType``. + .. _`oracledb`: https://oracle.github.io/python-oracledb/ Features removed in 5.0 diff --git a/tests/model_enums/tests.py b/tests/model_enums/tests.py index b010b35594..264a4b1703 100644 --- a/tests/model_enums/tests.py +++ b/tests/model_enums/tests.py @@ -6,6 +6,7 @@ import uuid from django.db import models from django.template import Context, Template from django.test import SimpleTestCase +from django.utils.deprecation import RemovedInDjango60Warning from django.utils.functional import Promise from django.utils.translation import gettext_lazy as _ @@ -315,3 +316,12 @@ class CustomChoicesTests(SimpleTestCase): class Identifier(uuid.UUID, models.Choices): A = "972ce4eb-a95f-4a56-9339-68c208a76f18" + + +class ChoicesMetaDeprecationTests(SimpleTestCase): + def test_deprecation_warning(self): + from django.db.models import enums + + msg = "ChoicesMeta is deprecated in favor of ChoicesType." + with self.assertWarnsMessage(RemovedInDjango60Warning, msg): + enums.ChoicesMeta