diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/inspectdb/models.py | 19 | ||||
-rw-r--r-- | tests/inspectdb/tests.py | 37 | ||||
-rw-r--r-- | tests/invalid_models_tests/test_ordinary_fields.py | 50 | ||||
-rw-r--r-- | tests/model_fields/test_charfield.py | 10 | ||||
-rw-r--r-- | tests/model_fields/test_textfield.py | 12 | ||||
-rw-r--r-- | tests/schema/tests.py | 152 |
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') |