summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/inspectdb/models.py19
-rw-r--r--tests/inspectdb/tests.py37
-rw-r--r--tests/invalid_models_tests/test_ordinary_fields.py50
-rw-r--r--tests/model_fields/test_charfield.py10
-rw-r--r--tests/model_fields/test_textfield.py12
-rw-r--r--tests/schema/tests.py152
6 files changed, 276 insertions, 4 deletions
diff --git a/tests/inspectdb/models.py b/tests/inspectdb/models.py
index d0076ce94f..4d2c224da9 100644
--- a/tests/inspectdb/models.py
+++ b/tests/inspectdb/models.py
@@ -1,4 +1,4 @@
-from django.db import models
+from django.db import connection, models
class People(models.Model):
@@ -79,6 +79,23 @@ class JSONFieldColumnType(models.Model):
}
+test_collation = connection.features.test_collations.get('non_default')
+
+
+class CharFieldDbCollation(models.Model):
+ char_field = models.CharField(max_length=10, db_collation=test_collation)
+
+ class Meta:
+ required_db_features = {'supports_collation_on_charfield'}
+
+
+class TextFieldDbCollation(models.Model):
+ text_field = models.TextField(db_collation=test_collation)
+
+ class Meta:
+ required_db_features = {'supports_collation_on_textfield'}
+
+
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 fa3a7ac21f..6815629e95 100644
--- a/tests/inspectdb/tests.py
+++ b/tests/inspectdb/tests.py
@@ -8,7 +8,7 @@ from django.db import connection
from django.db.backends.base.introspection import TableInfo
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
-from .models import PeopleMoreData
+from .models import PeopleMoreData, test_collation
def inspectdb_tables_only(table_name):
@@ -104,6 +104,41 @@ class InspectDBTestCase(TestCase):
self.assertIn('json_field = models.JSONField()', output)
self.assertIn('null_json_field = models.JSONField(blank=True, null=True)', output)
+ @skipUnlessDBFeature('supports_collation_on_charfield')
+ def test_char_field_db_collation(self):
+ out = StringIO()
+ call_command('inspectdb', 'inspectdb_charfielddbcollation', stdout=out)
+ output = out.getvalue()
+ if not connection.features.interprets_empty_strings_as_nulls:
+ self.assertIn(
+ "char_field = models.CharField(max_length=10, "
+ "db_collation='%s')" % test_collation,
+ output,
+ )
+ else:
+ self.assertIn(
+ "char_field = models.CharField(max_length=10, "
+ "db_collation='%s', blank=True, null=True)" % test_collation,
+ output,
+ )
+
+ @skipUnlessDBFeature('supports_collation_on_textfield')
+ def test_text_field_db_collation(self):
+ out = StringIO()
+ call_command('inspectdb', 'inspectdb_textfielddbcollation', stdout=out)
+ output = out.getvalue()
+ if not connection.features.interprets_empty_strings_as_nulls:
+ self.assertIn(
+ "text_field = models.TextField(db_collation='%s')" % test_collation,
+ output,
+ )
+ else:
+ self.assertIn(
+ "text_field = models.TextField(db_collation='%s, blank=True, "
+ "null=True)" % test_collation,
+ 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 a81f9eed90..6eddd853af 100644
--- a/tests/invalid_models_tests/test_ordinary_fields.py
+++ b/tests/invalid_models_tests/test_ordinary_fields.py
@@ -86,7 +86,7 @@ class BinaryFieldTests(SimpleTestCase):
@isolate_apps('invalid_models_tests')
-class CharFieldTests(SimpleTestCase):
+class CharFieldTests(TestCase):
def test_valid_field(self):
class Model(models.Model):
@@ -387,6 +387,30 @@ class CharFieldTests(SimpleTestCase):
)
])
+ def test_db_collation(self):
+ class Model(models.Model):
+ field = models.CharField(max_length=100, db_collation='anything')
+
+ field = Model._meta.get_field('field')
+ error = Error(
+ '%s does not support a database collation on CharFields.'
+ % connection.display_name,
+ id='fields.E190',
+ obj=field,
+ )
+ expected = [] if connection.features.supports_collation_on_charfield else [error]
+ self.assertEqual(field.check(databases=self.databases), expected)
+
+ def test_db_collation_required_db_features(self):
+ class Model(models.Model):
+ field = models.CharField(max_length=100, db_collation='anything')
+
+ class Meta:
+ required_db_features = {'supports_collation_on_charfield'}
+
+ field = Model._meta.get_field('field')
+ self.assertEqual(field.check(databases=self.databases), [])
+
@isolate_apps('invalid_models_tests')
class DateFieldTests(SimpleTestCase):
@@ -779,6 +803,30 @@ class TextFieldTests(TestCase):
)
])
+ def test_db_collation(self):
+ class Model(models.Model):
+ field = models.TextField(db_collation='anything')
+
+ field = Model._meta.get_field('field')
+ error = Error(
+ '%s does not support a database collation on TextFields.'
+ % connection.display_name,
+ id='fields.E190',
+ obj=field,
+ )
+ expected = [] if connection.features.supports_collation_on_textfield else [error]
+ self.assertEqual(field.check(databases=self.databases), expected)
+
+ def test_db_collation_required_db_features(self):
+ class Model(models.Model):
+ field = models.TextField(db_collation='anything')
+
+ class Meta:
+ required_db_features = {'supports_collation_on_textfield'}
+
+ field = Model._meta.get_field('field')
+ self.assertEqual(field.check(databases=self.databases), [])
+
@isolate_apps('invalid_models_tests')
class UUIDFieldTests(TestCase):
diff --git a/tests/model_fields/test_charfield.py b/tests/model_fields/test_charfield.py
index cf3ce0c4c5..a8ac895a11 100644
--- a/tests/model_fields/test_charfield.py
+++ b/tests/model_fields/test_charfield.py
@@ -44,6 +44,16 @@ class TestCharField(TestCase):
self.assertEqual(p2.title, Event.C)
+class TestMethods(SimpleTestCase):
+ def test_deconstruct(self):
+ field = models.CharField()
+ *_, kwargs = field.deconstruct()
+ self.assertEqual(kwargs, {})
+ field = models.CharField(db_collation='utf8_esperanto_ci')
+ *_, kwargs = field.deconstruct()
+ self.assertEqual(kwargs, {'db_collation': 'utf8_esperanto_ci'})
+
+
class ValidationTests(SimpleTestCase):
class Choices(models.TextChoices):
diff --git a/tests/model_fields/test_textfield.py b/tests/model_fields/test_textfield.py
index 82e7af8fd5..f0bce822a4 100644
--- a/tests/model_fields/test_textfield.py
+++ b/tests/model_fields/test_textfield.py
@@ -2,7 +2,7 @@ from unittest import skipIf
from django import forms
from django.db import connection, models
-from django.test import TestCase
+from django.test import SimpleTestCase, TestCase
from .models import Post
@@ -37,3 +37,13 @@ class TextFieldTests(TestCase):
p = Post.objects.create(title='Whatever', body='Smile 😀.')
p.refresh_from_db()
self.assertEqual(p.body, 'Smile 😀.')
+
+
+class TestMethods(SimpleTestCase):
+ def test_deconstruct(self):
+ field = models.TextField()
+ *_, kwargs = field.deconstruct()
+ self.assertEqual(kwargs, {})
+ field = models.TextField(db_collation='utf8_esperanto_ci')
+ *_, kwargs = field.deconstruct()
+ self.assertEqual(kwargs, {'db_collation': 'utf8_esperanto_ci'})
diff --git a/tests/schema/tests.py b/tests/schema/tests.py
index 8e992b4917..396d7c7c4f 100644
--- a/tests/schema/tests.py
+++ b/tests/schema/tests.py
@@ -185,6 +185,14 @@ class SchemaTests(TransactionTestCase):
counts['indexes'] += 1
return counts
+ def get_column_collation(self, table, column):
+ with connection.cursor() as cursor:
+ return next(
+ f.collation
+ for f in connection.introspection.get_table_description(cursor, table)
+ if f.name == column
+ )
+
def assertIndexOrder(self, table, index, order):
constraints = self.get_constraints(table)
self.assertIn(index, constraints)
@@ -3224,3 +3232,147 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor(atomic=True) as editor:
editor.alter_db_table(Foo, Foo._meta.db_table, 'renamed_table')
Foo._meta.db_table = 'renamed_table'
+
+ @isolate_apps('schema')
+ @skipUnlessDBFeature('supports_collation_on_charfield')
+ def test_db_collation_charfield(self):
+ collation = connection.features.test_collations['non_default']
+
+ class Foo(Model):
+ field = CharField(max_length=255, db_collation=collation)
+
+ class Meta:
+ app_label = 'schema'
+
+ self.isolated_local_models = [Foo]
+ with connection.schema_editor() as editor:
+ editor.create_model(Foo)
+
+ self.assertEqual(
+ self.get_column_collation(Foo._meta.db_table, 'field'),
+ collation,
+ )
+
+ @isolate_apps('schema')
+ @skipUnlessDBFeature('supports_collation_on_textfield')
+ def test_db_collation_textfield(self):
+ collation = connection.features.test_collations['non_default']
+
+ class Foo(Model):
+ field = TextField(db_collation=collation)
+
+ class Meta:
+ app_label = 'schema'
+
+ self.isolated_local_models = [Foo]
+ with connection.schema_editor() as editor:
+ editor.create_model(Foo)
+
+ self.assertEqual(
+ self.get_column_collation(Foo._meta.db_table, 'field'),
+ collation,
+ )
+
+ @skipUnlessDBFeature('supports_collation_on_charfield')
+ def test_add_field_db_collation(self):
+ with connection.schema_editor() as editor:
+ editor.create_model(Author)
+
+ collation = connection.features.test_collations['non_default']
+ new_field = CharField(max_length=255, db_collation=collation)
+ new_field.set_attributes_from_name('alias')
+ with connection.schema_editor() as editor:
+ editor.add_field(Author, new_field)
+ columns = self.column_classes(Author)
+ self.assertEqual(
+ columns['alias'][0],
+ connection.features.introspected_field_types['CharField'],
+ )
+ self.assertEqual(columns['alias'][1][8], collation)
+
+ @skipUnlessDBFeature('supports_collation_on_charfield')
+ def test_alter_field_db_collation(self):
+ with connection.schema_editor() as editor:
+ editor.create_model(Author)
+
+ collation = connection.features.test_collations['non_default']
+ old_field = Author._meta.get_field('name')
+ new_field = CharField(max_length=255, db_collation=collation)
+ new_field.set_attributes_from_name('name')
+ new_field.model = Author
+ with connection.schema_editor() as editor:
+ editor.alter_field(Author, old_field, new_field, strict=True)
+ self.assertEqual(
+ self.get_column_collation(Author._meta.db_table, 'name'),
+ collation,
+ )
+ with connection.schema_editor() as editor:
+ editor.alter_field(Author, new_field, old_field, strict=True)
+ self.assertIsNone(self.get_column_collation(Author._meta.db_table, 'name'))
+
+ @skipUnlessDBFeature('supports_collation_on_charfield')
+ def test_alter_field_type_and_db_collation(self):
+ with connection.schema_editor() as editor:
+ editor.create_model(Note)
+
+ collation = connection.features.test_collations['non_default']
+ old_field = Note._meta.get_field('info')
+ new_field = CharField(max_length=255, db_collation=collation)
+ new_field.set_attributes_from_name('info')
+ new_field.model = Note
+ with connection.schema_editor() as editor:
+ editor.alter_field(Note, old_field, new_field, strict=True)
+ columns = self.column_classes(Note)
+ self.assertEqual(
+ columns['info'][0],
+ connection.features.introspected_field_types['CharField'],
+ )
+ self.assertEqual(columns['info'][1][8], collation)
+ with connection.schema_editor() as editor:
+ editor.alter_field(Note, new_field, old_field, strict=True)
+ columns = self.column_classes(Note)
+ self.assertEqual(columns['info'][0], 'TextField')
+ self.assertIsNone(columns['info'][1][8])
+
+ @skipUnlessDBFeature(
+ 'supports_collation_on_charfield',
+ 'supports_non_deterministic_collations',
+ )
+ def test_ci_cs_db_collation(self):
+ cs_collation = connection.features.test_collations.get('cs')
+ ci_collation = connection.features.test_collations.get('ci')
+ try:
+ if connection.vendor == 'mysql':
+ cs_collation = 'latin1_general_cs'
+ elif connection.vendor == 'postgresql':
+ cs_collation = 'en-x-icu'
+ with connection.cursor() as cursor:
+ cursor.execute(
+ "CREATE COLLATION IF NOT EXISTS case_insensitive "
+ "(provider = icu, locale = 'und-u-ks-level2', "
+ "deterministic = false)"
+ )
+ ci_collation = 'case_insensitive'
+ # Create the table.
+ with connection.schema_editor() as editor:
+ editor.create_model(Author)
+ # Case-insensitive collation.
+ old_field = Author._meta.get_field('name')
+ new_field_ci = CharField(max_length=255, db_collation=ci_collation)
+ new_field_ci.set_attributes_from_name('name')
+ new_field_ci.model = Author
+ with connection.schema_editor() as editor:
+ editor.alter_field(Author, old_field, new_field_ci, strict=True)
+ Author.objects.create(name='ANDREW')
+ self.assertIs(Author.objects.filter(name='Andrew').exists(), True)
+ # Case-sensitive collation.
+ new_field_cs = CharField(max_length=255, db_collation=cs_collation)
+ new_field_cs.set_attributes_from_name('name')
+ new_field_cs.model = Author
+ with connection.schema_editor() as editor:
+ editor.alter_field(Author, new_field_ci, new_field_cs, strict=True)
+ self.assertIs(Author.objects.filter(name='Andrew').exists(), False)
+ finally:
+ if connection.vendor == 'postgresql':
+ with connection.cursor() as cursor:
+ cursor.execute('DROP COLLATION IF EXISTS case_insensitive')