diff --git a/django/contrib/postgres/fields/array.py b/django/contrib/postgres/fields/array.py index 43988e2e1c..84b487964f 100644 --- a/django/contrib/postgres/fields/array.py +++ b/django/contrib/postgres/fields/array.py @@ -1,4 +1,5 @@ import json +import warnings from django import forms from django.contrib.postgres import lookups @@ -8,6 +9,7 @@ from django.core import checks, exceptions from django.db.models import Field, Func, IntegerField, Transform, Value from django.db.models.fields.mixins import CheckFieldDefaultMixin from django.db.models.lookups import Exact, In +from django.utils.deprecation import RemovedInDjango51Warning from django.utils.translation import gettext_lazy as _ from ..utils import prefix_validation_error @@ -204,22 +206,22 @@ class ArrayField(CheckFieldDefaultMixin, Field): def formfield(self, **kwargs): if self.base_field.choices and "choices_form_class" not in kwargs: + obj = self.base_field defaults = { "choices_form_class": forms.TypedMultipleChoiceField, - "coerce": self.base_field.to_python, } - defaults.update(kwargs) - return self.base_field.formfield(**defaults) - elif not self.choices: + else: + obj = super() defaults = { "form_class": SimpleArrayField, "base_field": self.base_field.formfield(), "max_length": self.size, } - defaults.update(kwargs) - else: - raise NotImplementedError("Choices should be defined in base field.") - return super().formfield(**defaults) + if self.choices: + warnings.warn( + "Choices should be defined in base field.", RemovedInDjango51Warning + ) + return obj.formfield(**{**defaults, **kwargs}) class ArrayRHSMixin: diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 058ee3ed7f..5381a3b17f 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -1024,7 +1024,7 @@ class Field(RegisterLookupMixin): self.has_default() or "initial" in kwargs ) defaults["choices"] = self.get_choices(include_blank=include_blank) - defaults["coerce"] = kwargs.get("coerce", self.to_python) + defaults["coerce"] = self.to_python if self.null: defaults["empty_value"] = None if choices_form_class is not None: diff --git a/tests/postgres_tests/test_array.py b/tests/postgres_tests/test_array.py index d454f75ad5..268e96b078 100644 --- a/tests/postgres_tests/test_array.py +++ b/tests/postgres_tests/test_array.py @@ -3,6 +3,7 @@ import enum import json import unittest import uuid +import warnings from django import forms from django.core import checks, exceptions, serializers, validators @@ -14,6 +15,7 @@ from django.db.models.functions import Cast, JSONObject, Upper from django.test import TransactionTestCase, modify_settings, override_settings from django.test.utils import isolate_apps from django.utils import timezone +from django.utils.deprecation import RemovedInDjango51Warning from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase, PostgreSQLWidgetTestCase from .models import ( @@ -1134,12 +1136,15 @@ class TestChoiceFormField(PostgreSQLTestCase): ) def test_model_field_formfield_choices(self): - with self.assertRaises(NotImplementedError): - model_field = ArrayField( - models.CharField(max_length=27), choices=(("a1", "A1"), ("b1", "B1")) - ) - form_field = model_field.formfield() - form_field.clean(["a1", "b1"]) + model_field = ArrayField( + models.CharField(max_length=27), choices=(("a1", "A1"), ("b1", "B1")) + ) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + model_field.formfield() + assert len(w) == 1 + assert issubclass(w[-1].category, RemovedInDjango51Warning) + assert "Choices should be defined in base field." in str(w[-1].message) class TestSplitFormField(PostgreSQLSimpleTestCase):