mirror of
https://github.com/django/django.git
synced 2024-12-23 01:25:58 +00:00
Fixed #14094 -- Added support for unlimited CharField on PostgreSQL.
Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
This commit is contained in:
parent
78f163a4fb
commit
7eee1dca42
@ -339,7 +339,8 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
# Add max_length for all CharFields.
|
# Add max_length for all CharFields.
|
||||||
if field_type == "CharField" and row.display_size:
|
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:
|
if field_type in {"CharField", "TextField"} and row.collation:
|
||||||
field_params["db_collation"] = row.collation
|
field_params["db_collation"] = row.collation
|
||||||
|
@ -345,6 +345,9 @@ class BaseDatabaseFeatures:
|
|||||||
# Set to (exception, message) if null characters in text are disallowed.
|
# Set to (exception, message) if null characters in text are disallowed.
|
||||||
prohibits_null_characters_in_text_exception = None
|
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.
|
# Collation names for use by the Django test suite.
|
||||||
test_collations = {
|
test_collations = {
|
||||||
"ci": None, # Case-insensitive.
|
"ci": None, # Case-insensitive.
|
||||||
|
@ -80,6 +80,12 @@ from .operations import DatabaseOperations # NOQA isort:skip
|
|||||||
from .schema import DatabaseSchemaEditor # 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):
|
class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
vendor = "postgresql"
|
vendor = "postgresql"
|
||||||
display_name = "PostgreSQL"
|
display_name = "PostgreSQL"
|
||||||
@ -92,7 +98,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||||||
"BigAutoField": "bigint",
|
"BigAutoField": "bigint",
|
||||||
"BinaryField": "bytea",
|
"BinaryField": "bytea",
|
||||||
"BooleanField": "boolean",
|
"BooleanField": "boolean",
|
||||||
"CharField": "varchar(%(max_length)s)",
|
"CharField": _get_varchar_column,
|
||||||
"DateField": "date",
|
"DateField": "date",
|
||||||
"DateTimeField": "timestamp with time zone",
|
"DateTimeField": "timestamp with time zone",
|
||||||
"DecimalField": "numeric(%(max_digits)s, %(decimal_places)s)",
|
"DecimalField": "numeric(%(max_digits)s, %(decimal_places)s)",
|
||||||
|
@ -110,3 +110,4 @@ class DatabaseFeatures(BaseDatabaseFeatures):
|
|||||||
|
|
||||||
has_bit_xor = property(operator.attrgetter("is_postgresql_14"))
|
has_bit_xor = property(operator.attrgetter("is_postgresql_14"))
|
||||||
supports_covering_spgist_indexes = property(operator.attrgetter("is_postgresql_14"))
|
supports_covering_spgist_indexes = property(operator.attrgetter("is_postgresql_14"))
|
||||||
|
supports_unlimited_charfield = True
|
||||||
|
@ -817,9 +817,14 @@ class Field(RegisterLookupMixin):
|
|||||||
# exactly which wacky database column type you want to use.
|
# exactly which wacky database column type you want to use.
|
||||||
data = self.db_type_parameters(connection)
|
data = self.db_type_parameters(connection)
|
||||||
try:
|
try:
|
||||||
return connection.data_types[self.get_internal_type()] % data
|
column_type = connection.data_types[self.get_internal_type()]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
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):
|
def rel_db_type(self, connection):
|
||||||
"""
|
"""
|
||||||
@ -1130,14 +1135,19 @@ class BooleanField(Field):
|
|||||||
|
|
||||||
|
|
||||||
class CharField(Field):
|
class CharField(Field):
|
||||||
description = _("String (up to %(max_length)s)")
|
|
||||||
|
|
||||||
def __init__(self, *args, db_collation=None, **kwargs):
|
def __init__(self, *args, db_collation=None, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.db_collation = db_collation
|
self.db_collation = db_collation
|
||||||
if self.max_length is not None:
|
if self.max_length is not None:
|
||||||
self.validators.append(validators.MaxLengthValidator(self.max_length))
|
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):
|
def check(self, **kwargs):
|
||||||
databases = kwargs.get("databases") or []
|
databases = kwargs.get("databases") or []
|
||||||
return [
|
return [
|
||||||
@ -1148,6 +1158,12 @@ class CharField(Field):
|
|||||||
|
|
||||||
def _check_max_length_attribute(self, **kwargs):
|
def _check_max_length_attribute(self, **kwargs):
|
||||||
if self.max_length is None:
|
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 [
|
return [
|
||||||
checks.Error(
|
checks.Error(
|
||||||
"CharFields must define a 'max_length' attribute.",
|
"CharFields must define a 'max_length' attribute.",
|
||||||
|
@ -617,9 +617,11 @@ The default form widget for this field is a :class:`~django.forms.TextInput`.
|
|||||||
|
|
||||||
.. attribute:: CharField.max_length
|
.. 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
|
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::
|
.. 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
|
``max_length`` for some backends. Refer to the :doc:`database backend
|
||||||
notes </ref/databases>` for details.
|
notes </ref/databases>` for details.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.2
|
||||||
|
|
||||||
|
Support for unlimited ``VARCHAR`` columns was added on PostgreSQL.
|
||||||
|
|
||||||
.. attribute:: CharField.db_collation
|
.. attribute:: CharField.db_collation
|
||||||
|
|
||||||
Optional. The database collation name of the field.
|
Optional. The database collation name of the field.
|
||||||
|
@ -318,6 +318,10 @@ Models
|
|||||||
:meth:`~.RelatedManager.aclear`, :meth:`~.RelatedManager.aremove`, and
|
:meth:`~.RelatedManager.aclear`, :meth:`~.RelatedManager.aremove`, and
|
||||||
:meth:`~.RelatedManager.aset`.
|
: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
|
Requests and Responses
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -447,6 +447,16 @@ class TestFieldType(unittest.TestCase):
|
|||||||
"Boolean (Either True or False)",
|
"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):
|
def test_custom_fields(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
views.get_readable_field_data_type(CustomField()), "A custom field type"
|
views.get_readable_field_data_type(CustomField()), "A custom field type"
|
||||||
|
@ -106,6 +106,13 @@ class TextFieldDbCollation(models.Model):
|
|||||||
required_db_features = {"supports_collation_on_textfield"}
|
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):
|
class UniqueTogether(models.Model):
|
||||||
field1 = models.IntegerField()
|
field1 = models.IntegerField()
|
||||||
field2 = models.CharField(max_length=10)
|
field2 = models.CharField(max_length=10)
|
||||||
|
@ -184,6 +184,13 @@ class InspectDBTestCase(TestCase):
|
|||||||
output,
|
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):
|
def test_number_field_types(self):
|
||||||
"""Test introspection of various Django field types"""
|
"""Test introspection of various Django field types"""
|
||||||
assertFieldType = self.make_field_type_asserter()
|
assertFieldType = self.make_field_type_asserter()
|
||||||
|
@ -112,16 +112,18 @@ class CharFieldTests(TestCase):
|
|||||||
field = models.CharField()
|
field = models.CharField()
|
||||||
|
|
||||||
field = Model._meta.get_field("field")
|
field = Model._meta.get_field("field")
|
||||||
self.assertEqual(
|
expected = (
|
||||||
field.check(),
|
[]
|
||||||
[
|
if connection.features.supports_unlimited_charfield
|
||||||
|
else [
|
||||||
Error(
|
Error(
|
||||||
"CharFields must define a 'max_length' attribute.",
|
"CharFields must define a 'max_length' attribute.",
|
||||||
obj=field,
|
obj=field,
|
||||||
id="fields.E120",
|
id="fields.E120",
|
||||||
),
|
),
|
||||||
],
|
]
|
||||||
)
|
)
|
||||||
|
self.assertEqual(field.check(), expected)
|
||||||
|
|
||||||
def test_negative_max_length(self):
|
def test_negative_max_length(self):
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
|
@ -776,12 +776,12 @@ class TestOtherTypesExactQuerying(PostgreSQLTestCase):
|
|||||||
class TestChecks(PostgreSQLSimpleTestCase):
|
class TestChecks(PostgreSQLSimpleTestCase):
|
||||||
def test_field_checks(self):
|
def test_field_checks(self):
|
||||||
class MyModel(PostgreSQLModel):
|
class MyModel(PostgreSQLModel):
|
||||||
field = ArrayField(models.CharField())
|
field = ArrayField(models.CharField(max_length=-1))
|
||||||
|
|
||||||
model = MyModel()
|
model = MyModel()
|
||||||
errors = model.check()
|
errors = model.check()
|
||||||
self.assertEqual(len(errors), 1)
|
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.assertEqual(errors[0].id, "postgres.E001")
|
||||||
self.assertIn("max_length", errors[0].msg)
|
self.assertIn("max_length", errors[0].msg)
|
||||||
|
|
||||||
@ -837,12 +837,12 @@ class TestChecks(PostgreSQLSimpleTestCase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
class MyModel(PostgreSQLModel):
|
class MyModel(PostgreSQLModel):
|
||||||
field = ArrayField(ArrayField(models.CharField()))
|
field = ArrayField(ArrayField(models.CharField(max_length=-1)))
|
||||||
|
|
||||||
model = MyModel()
|
model = MyModel()
|
||||||
errors = model.check()
|
errors = model.check()
|
||||||
self.assertEqual(len(errors), 1)
|
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.assertEqual(errors[0].id, "postgres.E001")
|
||||||
self.assertIn("max_length", errors[0].msg)
|
self.assertIn("max_length", errors[0].msg)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user