summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrian Torres <atorresj@redhat.com>2022-11-10 19:31:31 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2022-12-28 12:31:04 +0100
commit7eee1dca420ee7c4451d24cd32fa08aed0dcbb36 (patch)
treef643059561e9699cc961845f63faf0a3664ff5d0
parent78f163a4fb3937aca2e71786fbdd51a0ef39629e (diff)
Fixed #14094 -- Added support for unlimited CharField on PostgreSQL.
Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
-rw-r--r--django/core/management/commands/inspectdb.py3
-rw-r--r--django/db/backends/base/features.py3
-rw-r--r--django/db/backends/postgresql/base.py8
-rw-r--r--django/db/backends/postgresql/features.py1
-rw-r--r--django/db/models/fields/__init__.py22
-rw-r--r--docs/ref/models/fields.txt10
-rw-r--r--docs/releases/4.2.txt4
-rw-r--r--tests/admin_docs/test_views.py10
-rw-r--r--tests/inspectdb/models.py7
-rw-r--r--tests/inspectdb/tests.py7
-rw-r--r--tests/invalid_models_tests/test_ordinary_fields.py10
-rw-r--r--tests/postgres_tests/test_array.py8
12 files changed, 78 insertions, 15 deletions
diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py
index 51d7866503..992c523a8e 100644
--- a/django/core/management/commands/inspectdb.py
+++ b/django/core/management/commands/inspectdb.py
@@ -339,7 +339,8 @@ class Command(BaseCommand):
# Add max_length for all CharFields.
if field_type == "CharField" and row.display_size:
- field_params["max_length"] = int(row.display_size)
+ if (size := int(row.display_size)) and size > 0:
+ field_params["max_length"] = size
if field_type in {"CharField", "TextField"} and row.collation:
field_params["db_collation"] = row.collation
diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py
index b5b6c5b55d..ef1fe88336 100644
--- a/django/db/backends/base/features.py
+++ b/django/db/backends/base/features.py
@@ -345,6 +345,9 @@ class BaseDatabaseFeatures:
# Set to (exception, message) if null characters in text are disallowed.
prohibits_null_characters_in_text_exception = None
+ # Does the backend support unlimited character columns?
+ supports_unlimited_charfield = False
+
# Collation names for use by the Django test suite.
test_collations = {
"ci": None, # Case-insensitive.
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
index ceea1bebad..99403f5322 100644
--- a/django/db/backends/postgresql/base.py
+++ b/django/db/backends/postgresql/base.py
@@ -80,6 +80,12 @@ from .operations import DatabaseOperations # NOQA isort:skip
from .schema import DatabaseSchemaEditor # NOQA isort:skip
+def _get_varchar_column(data):
+ if data["max_length"] is None:
+ return "varchar"
+ return "varchar(%(max_length)s)" % data
+
+
class DatabaseWrapper(BaseDatabaseWrapper):
vendor = "postgresql"
display_name = "PostgreSQL"
@@ -92,7 +98,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
"BigAutoField": "bigint",
"BinaryField": "bytea",
"BooleanField": "boolean",
- "CharField": "varchar(%(max_length)s)",
+ "CharField": _get_varchar_column,
"DateField": "date",
"DateTimeField": "timestamp with time zone",
"DecimalField": "numeric(%(max_digits)s, %(decimal_places)s)",
diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py
index 49658cd267..6c20dd87f0 100644
--- a/django/db/backends/postgresql/features.py
+++ b/django/db/backends/postgresql/features.py
@@ -110,3 +110,4 @@ class DatabaseFeatures(BaseDatabaseFeatures):
has_bit_xor = property(operator.attrgetter("is_postgresql_14"))
supports_covering_spgist_indexes = property(operator.attrgetter("is_postgresql_14"))
+ supports_unlimited_charfield = True
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index e3b47d173c..060e1be605 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -817,9 +817,14 @@ class Field(RegisterLookupMixin):
# exactly which wacky database column type you want to use.
data = self.db_type_parameters(connection)
try:
- return connection.data_types[self.get_internal_type()] % data
+ column_type = connection.data_types[self.get_internal_type()]
except KeyError:
return None
+ else:
+ # column_type is either a single-parameter function or a string.
+ if callable(column_type):
+ return column_type(data)
+ return column_type % data
def rel_db_type(self, connection):
"""
@@ -1130,14 +1135,19 @@ class BooleanField(Field):
class CharField(Field):
- description = _("String (up to %(max_length)s)")
-
def __init__(self, *args, db_collation=None, **kwargs):
super().__init__(*args, **kwargs)
self.db_collation = db_collation
if self.max_length is not None:
self.validators.append(validators.MaxLengthValidator(self.max_length))
+ @property
+ def description(self):
+ if self.max_length is not None:
+ return _("String (up to %(max_length)s)")
+ else:
+ return _("String (unlimited)")
+
def check(self, **kwargs):
databases = kwargs.get("databases") or []
return [
@@ -1148,6 +1158,12 @@ class CharField(Field):
def _check_max_length_attribute(self, **kwargs):
if self.max_length is None:
+ if (
+ connection.features.supports_unlimited_charfield
+ or "supports_unlimited_charfield"
+ in self.model._meta.required_db_features
+ ):
+ return []
return [
checks.Error(
"CharFields must define a 'max_length' attribute.",
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index d460e81ef4..4fb39fde82 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -617,9 +617,11 @@ The default form widget for this field is a :class:`~django.forms.TextInput`.
.. attribute:: CharField.max_length
- Required. The maximum length (in characters) of the field. The max_length
+ The maximum length (in characters) of the field. The ``max_length``
is enforced at the database level and in Django's validation using
- :class:`~django.core.validators.MaxLengthValidator`.
+ :class:`~django.core.validators.MaxLengthValidator`. It's required for all
+ database backends included with Django except PostgreSQL, which supports
+ unlimited ``VARCHAR`` columns.
.. note::
@@ -628,6 +630,10 @@ The default form widget for this field is a :class:`~django.forms.TextInput`.
``max_length`` for some backends. Refer to the :doc:`database backend
notes </ref/databases>` for details.
+ .. versionchanged:: 4.2
+
+ Support for unlimited ``VARCHAR`` columns was added on PostgreSQL.
+
.. attribute:: CharField.db_collation
Optional. The database collation name of the field.
diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt
index 90fdf9bd1b..755ebcb717 100644
--- a/docs/releases/4.2.txt
+++ b/docs/releases/4.2.txt
@@ -318,6 +318,10 @@ Models
:meth:`~.RelatedManager.aclear`, :meth:`~.RelatedManager.aremove`, and
:meth:`~.RelatedManager.aset`.
+* :attr:`CharField.max_length <django.db.models.CharField.max_length>` is no
+ longer required to be set on PostgreSQL, which supports unlimited ``VARCHAR``
+ columns.
+
Requests and Responses
~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/admin_docs/test_views.py b/tests/admin_docs/test_views.py
index d85f401855..29bbe40310 100644
--- a/tests/admin_docs/test_views.py
+++ b/tests/admin_docs/test_views.py
@@ -447,6 +447,16 @@ class TestFieldType(unittest.TestCase):
"Boolean (Either True or False)",
)
+ def test_char_fields(self):
+ self.assertEqual(
+ views.get_readable_field_data_type(fields.CharField(max_length=255)),
+ "String (up to 255)",
+ )
+ self.assertEqual(
+ views.get_readable_field_data_type(fields.CharField()),
+ "String (unlimited)",
+ )
+
def test_custom_fields(self):
self.assertEqual(
views.get_readable_field_data_type(CustomField()), "A custom field type"
diff --git a/tests/inspectdb/models.py b/tests/inspectdb/models.py
index 5db4d3bc73..9e6871ce46 100644
--- a/tests/inspectdb/models.py
+++ b/tests/inspectdb/models.py
@@ -106,6 +106,13 @@ class TextFieldDbCollation(models.Model):
required_db_features = {"supports_collation_on_textfield"}
+class CharFieldUnlimited(models.Model):
+ char_field = models.CharField(max_length=None)
+
+ class Meta:
+ required_db_features = {"supports_unlimited_charfield"}
+
+
class UniqueTogether(models.Model):
field1 = models.IntegerField()
field2 = models.CharField(max_length=10)
diff --git a/tests/inspectdb/tests.py b/tests/inspectdb/tests.py
index 2f26814625..ad929fd9bc 100644
--- a/tests/inspectdb/tests.py
+++ b/tests/inspectdb/tests.py
@@ -184,6 +184,13 @@ class InspectDBTestCase(TestCase):
output,
)
+ @skipUnlessDBFeature("supports_unlimited_charfield")
+ def test_char_field_unlimited(self):
+ out = StringIO()
+ call_command("inspectdb", "inspectdb_charfieldunlimited", stdout=out)
+ output = out.getvalue()
+ self.assertIn("char_field = models.CharField()", output)
+
def test_number_field_types(self):
"""Test introspection of various Django field types"""
assertFieldType = self.make_field_type_asserter()
diff --git a/tests/invalid_models_tests/test_ordinary_fields.py b/tests/invalid_models_tests/test_ordinary_fields.py
index 4e07c95956..4e37c48286 100644
--- a/tests/invalid_models_tests/test_ordinary_fields.py
+++ b/tests/invalid_models_tests/test_ordinary_fields.py
@@ -112,16 +112,18 @@ class CharFieldTests(TestCase):
field = models.CharField()
field = Model._meta.get_field("field")
- self.assertEqual(
- field.check(),
- [
+ expected = (
+ []
+ if connection.features.supports_unlimited_charfield
+ else [
Error(
"CharFields must define a 'max_length' attribute.",
obj=field,
id="fields.E120",
),
- ],
+ ]
)
+ self.assertEqual(field.check(), expected)
def test_negative_max_length(self):
class Model(models.Model):
diff --git a/tests/postgres_tests/test_array.py b/tests/postgres_tests/test_array.py
index 86e9d00b41..4808f88689 100644
--- a/tests/postgres_tests/test_array.py
+++ b/tests/postgres_tests/test_array.py
@@ -776,12 +776,12 @@ class TestOtherTypesExactQuerying(PostgreSQLTestCase):
class TestChecks(PostgreSQLSimpleTestCase):
def test_field_checks(self):
class MyModel(PostgreSQLModel):
- field = ArrayField(models.CharField())
+ field = ArrayField(models.CharField(max_length=-1))
model = MyModel()
errors = model.check()
self.assertEqual(len(errors), 1)
- # The inner CharField is missing a max_length.
+ # The inner CharField has a non-positive max_length.
self.assertEqual(errors[0].id, "postgres.E001")
self.assertIn("max_length", errors[0].msg)
@@ -837,12 +837,12 @@ class TestChecks(PostgreSQLSimpleTestCase):
"""
class MyModel(PostgreSQLModel):
- field = ArrayField(ArrayField(models.CharField()))
+ field = ArrayField(ArrayField(models.CharField(max_length=-1)))
model = MyModel()
errors = model.check()
self.assertEqual(len(errors), 1)
- # The inner CharField is missing a max_length.
+ # The inner CharField has a non-positive max_length.
self.assertEqual(errors[0].id, "postgres.E001")
self.assertIn("max_length", errors[0].msg)