summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarkus Holtermann <info@markusholtermann.eu>2015-01-29 15:14:55 +0100
committerMarkus Holtermann <info@markusholtermann.eu>2015-02-05 20:11:48 +0100
commit0204714b0bdf10d7558ee106de9a718407f3ec5a (patch)
tree94e70a1c342a6c0956984c76784306ae7380361f
parent44ad691558c88ac54483030b2c8b749788c4600e (diff)
downloaddjango-0204714b0bdf10d7558ee106de9a718407f3ec5a.tar.gz
Cleaned up schema tests
Thanks Tim Graham for the review.
-rw-r--r--tests/schema/fields.py4
-rw-r--r--tests/schema/models.py90
-rw-r--r--tests/schema/tests.py652
3 files changed, 315 insertions, 431 deletions
diff --git a/tests/schema/fields.py b/tests/schema/fields.py
index 4f70c96b0b..d4302a6677 100644
--- a/tests/schema/fields.py
+++ b/tests/schema/fields.py
@@ -52,3 +52,7 @@ class CustomManyToManyField(RelatedField):
_get_m2m_attr = ManyToManyField.__dict__['_get_m2m_attr']
_get_m2m_reverse_attr = ManyToManyField.__dict__['_get_m2m_reverse_attr']
_get_m2m_db_table = ManyToManyField.__dict__['_get_m2m_db_table']
+
+
+class InheritedManyToManyField(ManyToManyField):
+ pass
diff --git a/tests/schema/models.py b/tests/schema/models.py
index 6eba7ccae1..cf49d95abb 100644
--- a/tests/schema/models.py
+++ b/tests/schema/models.py
@@ -25,24 +25,9 @@ class AuthorWithDefaultHeight(models.Model):
apps = new_apps
-class AuthorWithM2M(models.Model):
- name = models.CharField(max_length=255)
-
- class Meta:
- apps = new_apps
-
-
-class AuthorWithM2MThrough(models.Model):
+class AuthorWithEvenLongerName(models.Model):
name = models.CharField(max_length=255)
- tags = models.ManyToManyField("schema.TagM2MTest", related_name="authors", through="AuthorTag")
-
- class Meta:
- apps = new_apps
-
-
-class AuthorTag(models.Model):
- author = models.ForeignKey("schema.AuthorWithM2MThrough")
- tag = models.ForeignKey("schema.TagM2MTest")
+ height = models.PositiveIntegerField(null=True, blank=True)
class Meta:
apps = new_apps
@@ -67,39 +52,21 @@ class BookWeak(models.Model):
apps = new_apps
-class BookWithO2O(models.Model):
- author = models.OneToOneField(Author)
- title = models.CharField(max_length=100, db_index=True)
- pub_date = models.DateTimeField()
+class BookWithLongName(models.Model):
+ author_foreign_key_with_really_long_field_name = models.ForeignKey(AuthorWithEvenLongerName)
class Meta:
apps = new_apps
- db_table = "schema_book"
-class BookWithM2M(models.Model):
- author = models.ForeignKey(Author)
+class BookWithO2O(models.Model):
+ author = models.OneToOneField(Author)
title = models.CharField(max_length=100, db_index=True)
pub_date = models.DateTimeField()
- tags = models.ManyToManyField("TagM2MTest", related_name="books")
-
- class Meta:
- apps = new_apps
-
-
-class TagThrough(models.Model):
- book = models.ForeignKey("schema.BookWithM2MThrough")
- tag = models.ForeignKey("schema.TagM2MTest")
-
- class Meta:
- apps = new_apps
-
-
-class BookWithM2MThrough(models.Model):
- tags = models.ManyToManyField("TagM2MTest", related_name="books", through=TagThrough)
class Meta:
apps = new_apps
+ db_table = "schema_book"
class BookWithSlug(models.Model):
@@ -113,6 +80,10 @@ class BookWithSlug(models.Model):
db_table = "schema_book"
+class Note(models.Model):
+ info = models.TextField()
+
+
class Tag(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
@@ -121,21 +92,21 @@ class Tag(models.Model):
apps = new_apps
-class TagM2MTest(models.Model):
+class TagIndexed(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
class Meta:
apps = new_apps
+ index_together = [["slug", "title"]]
-class TagIndexed(models.Model):
+class TagM2MTest(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
class Meta:
apps = new_apps
- index_together = [["slug", "title"]]
class TagUniqueRename(models.Model):
@@ -147,30 +118,6 @@ class TagUniqueRename(models.Model):
db_table = "schema_tag"
-class UniqueTest(models.Model):
- year = models.IntegerField()
- slug = models.SlugField(unique=False)
-
- class Meta:
- apps = new_apps
- unique_together = ["year", "slug"]
-
-
-class AuthorWithEvenLongerName(models.Model):
- name = models.CharField(max_length=255)
- height = models.PositiveIntegerField(null=True, blank=True)
-
- class Meta:
- apps = new_apps
-
-
-class BookWithLongName(models.Model):
- author_foreign_key_with_really_long_field_name = models.ForeignKey(AuthorWithEvenLongerName)
-
- class Meta:
- apps = new_apps
-
-
# Based on tests/reserved_names/models.py
@python_2_unicode_compatible
class Thing(models.Model):
@@ -183,5 +130,10 @@ class Thing(models.Model):
return self.when
-class Note(models.Model):
- info = models.TextField()
+class UniqueTest(models.Model):
+ year = models.IntegerField()
+ slug = models.SlugField(unique=False)
+
+ class Meta:
+ apps = new_apps
+ unique_together = ["year", "slug"]
diff --git a/tests/schema/tests.py b/tests/schema/tests.py
index 12fac76579..863392084b 100644
--- a/tests/schema/tests.py
+++ b/tests/schema/tests.py
@@ -1,17 +1,21 @@
import datetime
+import itertools
import unittest
-from django.test import TransactionTestCase
from django.db import connection, DatabaseError, IntegrityError, OperationalError
-from django.db.models.fields import (BinaryField, BooleanField, CharField, IntegerField,
- PositiveIntegerField, SlugField, TextField)
+from django.db.models import Model
+from django.db.models.fields import (BinaryField, BooleanField, CharField, DateTimeField,
+ IntegerField, PositiveIntegerField, SlugField, TextField)
from django.db.models.fields.related import ForeignKey, ManyToManyField, OneToOneField
from django.db.transaction import atomic
-from .fields import CustomManyToManyField
-from .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName,
- BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename,
- UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough,
- AuthorWithEvenLongerName, BookWeak, Note, BookWithO2O)
+from django.test import TransactionTestCase
+
+from .fields import CustomManyToManyField, InheritedManyToManyField
+from .models import (
+ Author, AuthorWithDefaultHeight, AuthorWithEvenLongerName, Book, BookWeak,
+ BookWithLongName, BookWithO2O, BookWithSlug, Note, Tag, TagIndexed,
+ TagM2MTest, TagUniqueRename, Thing, UniqueTest, new_apps
+)
class SchemaTests(TransactionTestCase):
@@ -26,24 +30,34 @@ class SchemaTests(TransactionTestCase):
available_apps = []
models = [
- Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug,
- BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest,
- Thing, TagThrough, BookWithM2MThrough, AuthorWithEvenLongerName,
- BookWeak, BookWithO2O,
+ Author, AuthorWithDefaultHeight, AuthorWithEvenLongerName, Book,
+ BookWeak, BookWithLongName, BookWithO2O, BookWithSlug, Note, Tag,
+ TagIndexed, TagM2MTest, TagUniqueRename, Thing, UniqueTest,
]
# Utility functions
+ def setUp(self):
+ # local_models should contain test dependent model classes that will be
+ # automatically removed from the app cache on test tear down.
+ self.local_models = []
+
def tearDown(self):
# Delete any tables made for our models
self.delete_tables()
+ new_apps.clear_cache()
+ for model in new_apps.get_models():
+ model._meta._expire_cache()
+ if 'schema' in new_apps.all_models:
+ for model in self.local_models:
+ del new_apps.all_models['schema'][model._meta.model_name]
def delete_tables(self):
"Deletes all model tables for our models for a clean test environment"
with connection.cursor() as cursor:
connection.disable_constraint_checking()
table_names = connection.introspection.table_names(cursor)
- for model in self.models:
+ for model in itertools.chain(SchemaTests.models, self.local_models):
# Remove any M2M tables first
for field in model._meta.local_many_to_many:
with atomic():
@@ -134,15 +148,11 @@ class SchemaTests(TransactionTestCase):
pub_date=datetime.datetime.now(),
)
# Repoint the FK constraint
+ old_field = Book._meta.get_field("author")
new_field = ForeignKey(Tag)
new_field.set_attributes_from_name("author")
with connection.schema_editor() as editor:
- editor.alter_field(
- Book,
- Book._meta.get_field("author"),
- new_field,
- strict=True,
- )
+ editor.alter_field(Book, old_field, new_field, strict=True)
# Make sure the new FK constraint is present
constraints = self.get_constraints(Book._meta.db_table)
for name, details in constraints.items():
@@ -173,25 +183,17 @@ class SchemaTests(TransactionTestCase):
new_field = ForeignKey(Tag, db_constraint=False)
new_field.set_attributes_from_name("tag")
with connection.schema_editor() as editor:
- editor.add_field(
- Author,
- new_field,
- )
+ editor.add_field(Author, new_field)
# Make sure no FK constraint is present
constraints = self.get_constraints(Author._meta.db_table)
for name, details in constraints.items():
if details['columns'] == ["tag_id"] and details['foreign_key']:
self.fail("FK constraint for tag_id found")
# Alter to one with a constraint
- new_field_2 = ForeignKey(Tag)
- new_field_2.set_attributes_from_name("tag")
- with connection.schema_editor() as editor:
- editor.alter_field(
- Author,
- new_field,
- new_field_2,
- strict=True,
- )
+ new_field2 = ForeignKey(Tag)
+ new_field2.set_attributes_from_name("tag")
+ with connection.schema_editor() as editor:
+ editor.alter_field(Author, new_field, new_field2, strict=True)
# Make sure the new FK constraint is present
constraints = self.get_constraints(Author._meta.db_table)
for name, details in constraints.items():
@@ -201,45 +203,56 @@ class SchemaTests(TransactionTestCase):
else:
self.fail("No FK constraint for tag_id found")
# Alter to one without a constraint again
- new_field_2 = ForeignKey(Tag)
- new_field_2.set_attributes_from_name("tag")
- with connection.schema_editor() as editor:
- editor.alter_field(
- Author,
- new_field_2,
- new_field,
- strict=True,
- )
+ new_field2 = ForeignKey(Tag)
+ new_field2.set_attributes_from_name("tag")
+ with connection.schema_editor() as editor:
+ editor.alter_field(Author, new_field2, new_field, strict=True)
# Make sure no FK constraint is present
constraints = self.get_constraints(Author._meta.db_table)
for name, details in constraints.items():
if details['columns'] == ["tag_id"] and details['foreign_key']:
self.fail("FK constraint for tag_id found")
- @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
- def test_m2m_db_constraint(self):
+ def _test_m2m_db_constraint(self, M2MFieldClass):
+ class LocalAuthorWithM2M(Model):
+ name = CharField(max_length=255)
+
+ class Meta:
+ apps = new_apps
+
+ self.local_models = [LocalAuthorWithM2M]
+
# Create the table
with connection.schema_editor() as editor:
editor.create_model(Tag)
- editor.create_model(Author)
+ editor.create_model(LocalAuthorWithM2M)
# Check that initial tables are there
- list(Author.objects.all())
+ list(LocalAuthorWithM2M.objects.all())
list(Tag.objects.all())
# Make a db_constraint=False FK
- new_field = ManyToManyField("schema.Tag", related_name="authors", db_constraint=False)
- new_field.contribute_to_class(Author, "tags")
+ new_field = M2MFieldClass(Tag, related_name="authors", db_constraint=False)
+ new_field.contribute_to_class(LocalAuthorWithM2M, "tags")
# Add the field
with connection.schema_editor() as editor:
- editor.add_field(
- Author,
- new_field,
- )
+ editor.add_field(LocalAuthorWithM2M, new_field)
# Make sure no FK constraint is present
constraints = self.get_constraints(new_field.rel.through._meta.db_table)
for name, details in constraints.items():
if details['columns'] == ["tag_id"] and details['foreign_key']:
self.fail("FK constraint for tag_id found")
+ @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
+ def test_m2m_db_constraint(self):
+ self._test_m2m_db_constraint(ManyToManyField)
+
+ @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
+ def test_m2m_db_constraint_custom(self):
+ self._test_m2m_db_constraint(CustomManyToManyField)
+
+ @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
+ def test_m2m_db_constraint_inherited(self):
+ self._test_m2m_db_constraint(InheritedManyToManyField)
+
def test_add_field(self):
"""
Tests adding fields to models
@@ -254,10 +267,7 @@ class SchemaTests(TransactionTestCase):
new_field = IntegerField(null=True)
new_field.set_attributes_from_name("age")
with connection.schema_editor() as editor:
- editor.add_field(
- Author,
- new_field,
- )
+ editor.add_field(Author, new_field)
# Ensure the field is right afterwards
columns = self.column_classes(Author)
self.assertEqual(columns['age'][0], "IntegerField")
@@ -280,10 +290,7 @@ class SchemaTests(TransactionTestCase):
new_field = CharField(max_length=30, default="Godwin")
new_field.set_attributes_from_name("surname")
with connection.schema_editor() as editor:
- editor.add_field(
- Author,
- new_field,
- )
+ editor.add_field(Author, new_field)
# Ensure the field is right afterwards
columns = self.column_classes(Author)
self.assertEqual(columns['surname'][0], "CharField")
@@ -308,10 +315,7 @@ class SchemaTests(TransactionTestCase):
new_field = BooleanField(default=False)
new_field.set_attributes_from_name("awesome")
with connection.schema_editor() as editor:
- editor.add_field(
- Author,
- new_field,
- )
+ editor.add_field(Author, new_field)
# Ensure the field is right afterwards
columns = self.column_classes(Author)
# BooleanField are stored as TINYINT(1) on MySQL.
@@ -345,10 +349,7 @@ class SchemaTests(TransactionTestCase):
new_field = TestTransformField(default={1: 2})
new_field.set_attributes_from_name("thing")
with connection.schema_editor() as editor:
- editor.add_field(
- Author,
- new_field,
- )
+ editor.add_field(Author, new_field)
# Ensure the field is there
columns = self.column_classes(Author)
field_type, field_info = columns['thing']
@@ -367,10 +368,7 @@ class SchemaTests(TransactionTestCase):
new_field = BinaryField(blank=True)
new_field.set_attributes_from_name("bits")
with connection.schema_editor() as editor:
- editor.add_field(
- Author,
- new_field,
- )
+ editor.add_field(Author, new_field)
# Ensure the field is right afterwards
columns = self.column_classes(Author)
# MySQL annoyingly uses the same backend, so it'll come back as one of
@@ -389,15 +387,11 @@ class SchemaTests(TransactionTestCase):
self.assertEqual(columns['name'][0], "CharField")
self.assertEqual(bool(columns['name'][1][6]), bool(connection.features.interprets_empty_strings_as_nulls))
# Alter the name field to a TextField
+ old_field = Author._meta.get_field("name")
new_field = TextField(null=True)
new_field.set_attributes_from_name("name")
with connection.schema_editor() as editor:
- editor.alter_field(
- Author,
- Author._meta.get_field("name"),
- new_field,
- strict=True,
- )
+ editor.alter_field(Author, old_field, new_field, strict=True)
# Ensure the field is right afterwards
columns = self.column_classes(Author)
self.assertEqual(columns['name'][0], "TextField")
@@ -406,12 +400,7 @@ class SchemaTests(TransactionTestCase):
new_field2 = TextField(null=False)
new_field2.set_attributes_from_name("name")
with connection.schema_editor() as editor:
- editor.alter_field(
- Author,
- new_field,
- new_field2,
- strict=True,
- )
+ editor.alter_field(Author, new_field, new_field2, strict=True)
# Ensure the field is right afterwards
columns = self.column_classes(Author)
self.assertEqual(columns['name'][0], "TextField")
@@ -420,15 +409,14 @@ class SchemaTests(TransactionTestCase):
def test_alter_text_field(self):
# Regression for "BLOB/TEXT column 'info' can't have a default value")
# on MySQL.
+ # Create the table
+ with connection.schema_editor() as editor:
+ editor.create_model(Note)
+ old_field = Note._meta.get_field("info")
new_field = TextField(blank=True)
new_field.set_attributes_from_name("info")
with connection.schema_editor() as editor:
- editor.alter_field(
- Note,
- Note._meta.get_field("info"),
- new_field,
- strict=True,
- )
+ editor.alter_field(Note, old_field, new_field, strict=True)
def test_alter_null_to_not_null(self):
"""
@@ -447,14 +435,11 @@ class SchemaTests(TransactionTestCase):
self.assertEqual(Author.objects.get(name='Not null author').height, 12)
self.assertIsNone(Author.objects.get(name='Null author').height)
# Alter the height field to NOT NULL with default
+ old_field = Author._meta.get_field("height")
new_field = PositiveIntegerField(default=42)
new_field.set_attributes_from_name("height")
with connection.schema_editor() as editor:
- editor.alter_field(
- Author,
- Author._meta.get_field("height"),
- new_field
- )
+ editor.alter_field(Author, old_field, new_field)
# Ensure the field is right afterwards
columns = self.column_classes(Author)
self.assertFalse(columns['height'][1][6])
@@ -475,14 +460,11 @@ class SchemaTests(TransactionTestCase):
columns = self.column_classes(AuthorWithDefaultHeight)
self.assertTrue(columns['height'][1][6])
# Alter the height field to NOT NULL keeping the previous default
+ old_field = AuthorWithDefaultHeight._meta.get_field("height")
new_field = PositiveIntegerField(default=42)
new_field.set_attributes_from_name("height")
with connection.schema_editor() as editor:
- editor.alter_field(
- AuthorWithDefaultHeight,
- AuthorWithDefaultHeight._meta.get_field("height"),
- new_field,
- )
+ editor.alter_field(AuthorWithDefaultHeight, old_field, new_field)
# Ensure the field is right afterwards
columns = self.column_classes(AuthorWithDefaultHeight)
self.assertFalse(columns['height'][1][6])
@@ -508,15 +490,11 @@ class SchemaTests(TransactionTestCase):
else:
self.fail("No FK constraint for author_id found")
# Alter the FK
+ old_field = Book._meta.get_field("author")
new_field = ForeignKey(Author, editable=False)
new_field.set_attributes_from_name("author")
with connection.schema_editor() as editor:
- editor.alter_field(
- Book,
- Book._meta.get_field("author"),
- new_field,
- strict=True,
- )
+ editor.alter_field(Book, old_field, new_field, strict=True)
# Ensure the field is right afterwards
columns = self.column_classes(Book)
self.assertEqual(columns['author_id'][0], "IntegerField")
@@ -556,15 +534,11 @@ class SchemaTests(TransactionTestCase):
author_is_fk = True
self.assertTrue(author_is_fk, "No FK constraint for author_id found")
# Alter the OneToOneField to ForeignKey
+ old_field = BookWithO2O._meta.get_field("author")
new_field = ForeignKey(Author)
new_field.set_attributes_from_name("author")
with connection.schema_editor() as editor:
- editor.alter_field(
- BookWithO2O,
- BookWithO2O._meta.get_field("author"),
- new_field,
- strict=True,
- )
+ editor.alter_field(BookWithO2O, old_field, new_field, strict=True)
# Ensure the field is right afterwards
columns = self.column_classes(Book)
self.assertEqual(columns['author_id'][0], "IntegerField")
@@ -606,15 +580,11 @@ class SchemaTests(TransactionTestCase):
author_is_fk = True
self.assertTrue(author_is_fk, "No FK constraint for author_id found")
# Alter the ForeignKey to OneToOneField
+ old_field = Book._meta.get_field("author")
new_field = OneToOneField(Author)
new_field.set_attributes_from_name("author")
with connection.schema_editor() as editor:
- editor.alter_field(
- Book,
- Book._meta.get_field("author"),
- new_field,
- strict=True,
- )
+ editor.alter_field(Book, old_field, new_field, strict=True)
# Ensure the field is right afterwards
columns = self.column_classes(BookWithO2O)
self.assertEqual(columns['author_id'][0], "IntegerField")
@@ -639,17 +609,12 @@ class SchemaTests(TransactionTestCase):
with connection.schema_editor() as editor:
editor.create_model(Author)
+ old_field = Author._meta.get_field("id")
new_field = IntegerField(primary_key=True)
new_field.set_attributes_from_name("id")
new_field.model = Author
with connection.schema_editor() as editor:
- editor.alter_field(
- Author,
- Author._meta.get_field("id"),
- new_field,
- strict=True,
- )
-
+ editor.alter_field(Author, old_field, new_field, strict=True)
# This will fail if DROP DEFAULT is inadvertently executed on this
# field which drops the id sequence, at least on PostgreSQL.
Author.objects.create(name='Foo')
@@ -666,127 +631,202 @@ class SchemaTests(TransactionTestCase):
self.assertEqual(columns['name'][0], "CharField")
self.assertNotIn("display_name", columns)
# Alter the name field's name
+ old_field = Author._meta.get_field("name")
new_field = CharField(max_length=254)
new_field.set_attributes_from_name("display_name")
with connection.schema_editor() as editor:
- editor.alter_field(
- Author,
- Author._meta.get_field("name"),
- new_field,
- strict=True,
- )
+ editor.alter_field(Author, old_field, new_field, strict=True)
# Ensure the field is right afterwards
columns = self.column_classes(Author)
self.assertEqual(columns['display_name'][0], "CharField")
self.assertNotIn("name", columns)
- def test_m2m_create(self):
+ def _test_m2m_create(self, M2MFieldClass):
"""
Tests M2M fields on models during creation
"""
+ class LocalBookWithM2M(Model):
+ author = ForeignKey(Author)
+ title = CharField(max_length=100, db_index=True)
+ pub_date = DateTimeField()
+ tags = M2MFieldClass("TagM2MTest", related_name="books")
+
+ class Meta:
+ apps = new_apps
+
+ self.local_models = [LocalBookWithM2M]
+
# Create the tables
with connection.schema_editor() as editor:
editor.create_model(Author)
editor.create_model(TagM2MTest)
- editor.create_model(BookWithM2M)
+ editor.create_model(LocalBookWithM2M)
# Ensure there is now an m2m table there
- columns = self.column_classes(BookWithM2M._meta.get_field("tags").rel.through)
+ columns = self.column_classes(LocalBookWithM2M._meta.get_field("tags").rel.through)
self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField")
- def test_m2m_create_through(self):
+ def test_m2m_create(self):
+ self._test_m2m_create(ManyToManyField)
+
+ def test_m2m_create_custom(self):
+ self._test_m2m_create(CustomManyToManyField)
+
+ def test_m2m_create_inherited(self):
+ self._test_m2m_create(InheritedManyToManyField)
+
+ def _test_m2m_create_through(self, M2MFieldClass):
"""
Tests M2M fields on models during creation with through models
"""
+ class LocalTagThrough(Model):
+ book = ForeignKey("schema.LocalBookWithM2MThrough")
+ tag = ForeignKey("schema.TagM2MTest")
+
+ class Meta:
+ apps = new_apps
+
+ class LocalBookWithM2MThrough(Model):
+ tags = M2MFieldClass("TagM2MTest", related_name="books", through=LocalTagThrough)
+
+ class Meta:
+ apps = new_apps
+
+ self.local_models = [LocalTagThrough, LocalBookWithM2MThrough]
+
# Create the tables
with connection.schema_editor() as editor:
- editor.create_model(TagThrough)
+ editor.create_model(LocalTagThrough)
editor.create_model(TagM2MTest)
- editor.create_model(BookWithM2MThrough)
+ editor.create_model(LocalBookWithM2MThrough)
# Ensure there is now an m2m table there
- columns = self.column_classes(TagThrough)
+ columns = self.column_classes(LocalTagThrough)
self.assertEqual(columns['book_id'][0], "IntegerField")
self.assertEqual(columns['tag_id'][0], "IntegerField")
- def test_m2m(self):
+ def test_m2m_create_through(self):
+ self._test_m2m_create_through(ManyToManyField)
+
+ def test_m2m_create_through_custom(self):
+ self._test_m2m_create_through(CustomManyToManyField)
+
+ def test_m2m_create_through_inherited(self):
+ self._test_m2m_create_through(InheritedManyToManyField)
+
+ def _test_m2m(self, M2MFieldClass):
"""
Tests adding/removing M2M fields on models
"""
+ class LocalAuthorWithM2M(Model):
+ name = CharField(max_length=255)
+
+ class Meta:
+ apps = new_apps
+
+ self.local_models = [LocalAuthorWithM2M]
+
# Create the tables
with connection.schema_editor() as editor:
- editor.create_model(AuthorWithM2M)
+ editor.create_model(LocalAuthorWithM2M)
editor.create_model(TagM2MTest)
# Create an M2M field
- new_field = ManyToManyField("schema.TagM2MTest", related_name="authors")
- new_field.contribute_to_class(AuthorWithM2M, "tags")
- try:
- # Ensure there's no m2m table there
- self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through)
- # Add the field
- with connection.schema_editor() as editor:
- editor.add_field(
- Author,
- new_field,
- )
- # Ensure there is now an m2m table there
- columns = self.column_classes(new_field.rel.through)
- self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField")
-
- # "Alter" the field. This should not rename the DB table to itself.
- with connection.schema_editor() as editor:
- editor.alter_field(
- Author,
- new_field,
- new_field,
- )
+ new_field = M2MFieldClass("schema.TagM2MTest", related_name="authors")
+ new_field.contribute_to_class(LocalAuthorWithM2M, "tags")
+ # Ensure there's no m2m table there
+ self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through)
+ # Add the field
+ with connection.schema_editor() as editor:
+ editor.add_field(LocalAuthorWithM2M, new_field)
+ # Ensure there is now an m2m table there
+ columns = self.column_classes(new_field.rel.through)
+ self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField")
- # Remove the M2M table again
- with connection.schema_editor() as editor:
- editor.remove_field(
- Author,
- new_field,
- )
- # Ensure there's no m2m table there
- self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through)
- finally:
- # Cleanup model states
- AuthorWithM2M._meta.local_many_to_many.remove(new_field)
+ # "Alter" the field. This should not rename the DB table to itself.
+ with connection.schema_editor() as editor:
+ editor.alter_field(LocalAuthorWithM2M, new_field, new_field)
- def test_m2m_through_alter(self):
+ # Remove the M2M table again
+ with connection.schema_editor() as editor:
+ editor.remove_field(LocalAuthorWithM2M, new_field)
+ # Ensure there's no m2m table there
+ self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through)
+
+ def test_m2m(self):
+ self._test_m2m(ManyToManyField)
+
+ def test_m2m_custom(self):
+ self._test_m2m(CustomManyToManyField)
+
+ def test_m2m_inherited(self):
+ self._test_m2m(InheritedManyToManyField)
+
+ def _test_m2m_through_alter(self, M2MFieldClass):
"""
Tests altering M2Ms with explicit through models (should no-op)
"""
+ class LocalAuthorTag(Model):
+ author = ForeignKey("schema.LocalAuthorWithM2MThrough")
+ tag = ForeignKey("schema.TagM2MTest")
+
+ class Meta:
+ apps = new_apps
+
+ class LocalAuthorWithM2MThrough(Model):
+ name = CharField(max_length=255)
+ tags = M2MFieldClass("schema.TagM2MTest", related_name="authors", through=LocalAuthorTag)
+
+ class Meta:
+ apps = new_apps
+
+ self.local_models = [LocalAuthorTag, LocalAuthorWithM2MThrough]
+
# Create the tables
with connection.schema_editor() as editor:
- editor.create_model(AuthorTag)
- editor.create_model(AuthorWithM2MThrough)
+ editor.create_model(LocalAuthorTag)
+ editor.create_model(LocalAuthorWithM2MThrough)
editor.create_model(TagM2MTest)
# Ensure the m2m table is there
- self.assertEqual(len(self.column_classes(AuthorTag)), 3)
+ self.assertEqual(len(self.column_classes(LocalAuthorTag)), 3)
# "Alter" the field's blankness. This should not actually do anything.
+ old_field = LocalAuthorWithM2MThrough._meta.get_field("tags")
+ new_field = M2MFieldClass("schema.TagM2MTest", related_name="authors", through=LocalAuthorTag)
+ new_field.contribute_to_class(LocalAuthorWithM2MThrough, "tags")
with connection.schema_editor() as editor:
- old_field = AuthorWithM2MThrough._meta.get_field("tags")
- new_field = ManyToManyField("schema.TagM2MTest", related_name="authors", through="AuthorTag")
- new_field.contribute_to_class(AuthorWithM2MThrough, "tags")
- editor.alter_field(
- Author,
- old_field,
- new_field,
- )
+ editor.alter_field(LocalAuthorWithM2MThrough, old_field, new_field)
# Ensure the m2m table is still there
- self.assertEqual(len(self.column_classes(AuthorTag)), 3)
+ self.assertEqual(len(self.column_classes(LocalAuthorTag)), 3)
- def test_m2m_repoint(self):
+ def test_m2m_through_alter(self):
+ self._test_m2m_through_alter(ManyToManyField)
+
+ def test_m2m_through_alter_custom(self):
+ self._test_m2m_through_alter(CustomManyToManyField)
+
+ def test_m2m_through_alter_inherited(self):
+ self._test_m2m_through_alter(InheritedManyToManyField)
+
+ def _test_m2m_repoint(self, M2MFieldClass):
"""
Tests repointing M2M fields
"""
+ class LocalBookWithM2M(Model):
+ author = ForeignKey(Author)
+ title = CharField(max_length=100, db_index=True)
+ pub_date = DateTimeField()
+ tags = M2MFieldClass("TagM2MTest", related_name="books")
+
+ class Meta:
+ apps = new_apps
+
+ self.local_models = [LocalBookWithM2M]
+
# Create the tables
with connection.schema_editor() as editor:
editor.create_model(Author)
- editor.create_model(BookWithM2M)
+ editor.create_model(LocalBookWithM2M)
editor.create_model(TagM2MTest)
editor.create_model(UniqueTest)
# Ensure the M2M exists and points to TagM2MTest
- constraints = self.get_constraints(BookWithM2M._meta.get_field("tags").rel.through._meta.db_table)
+ constraints = self.get_constraints(LocalBookWithM2M._meta.get_field("tags").rel.through._meta.db_table)
if connection.features.supports_foreign_keys:
for name, details in constraints.items():
if details['columns'] == ["tagm2mtest_id"] and details['foreign_key']:
@@ -795,33 +835,31 @@ class SchemaTests(TransactionTestCase):
else:
self.fail("No FK constraint for tagm2mtest_id found")
# Repoint the M2M
- new_field = ManyToManyField(UniqueTest)
- new_field.contribute_to_class(BookWithM2M, "uniques")
- try:
- with connection.schema_editor() as editor:
- editor.alter_field(
- Author,
- BookWithM2M._meta.get_field("tags"),
- new_field,
- )
- # Ensure old M2M is gone
- self.assertRaises(DatabaseError, self.column_classes, BookWithM2M._meta.get_field("tags").rel.through)
- # Ensure the new M2M exists and points to UniqueTest
- constraints = self.get_constraints(new_field.rel.through._meta.db_table)
- if connection.features.supports_foreign_keys:
- for name, details in constraints.items():
- if details['columns'] == ["uniquetest_id"] and details['foreign_key']:
- self.assertEqual(details['foreign_key'], ('schema_uniquetest', 'id'))
- break
- else:
- self.fail("No FK constraint for uniquetest_id found")
- finally:
- # Cleanup through table separately
- with connection.schema_editor() as editor:
- editor.remove_field(BookWithM2M, BookWithM2M._meta.get_field("uniques"))
- # Cleanup model states
- BookWithM2M._meta.local_many_to_many.remove(new_field)
- BookWithM2M._meta._expire_cache()
+ old_field = LocalBookWithM2M._meta.get_field("tags")
+ new_field = M2MFieldClass(UniqueTest)
+ new_field.contribute_to_class(LocalBookWithM2M, "uniques")
+ with connection.schema_editor() as editor:
+ editor.alter_field(LocalBookWithM2M, old_field, new_field)
+ # Ensure old M2M is gone
+ self.assertRaises(DatabaseError, self.column_classes, LocalBookWithM2M._meta.get_field("tags").rel.through)
+ # Ensure the new M2M exists and points to UniqueTest
+ constraints = self.get_constraints(new_field.rel.through._meta.db_table)
+ if connection.features.supports_foreign_keys:
+ for name, details in constraints.items():
+ if details['columns'] == ["uniquetest_id"] and details['foreign_key']:
+ self.assertEqual(details['foreign_key'], ('schema_uniquetest', 'id'))
+ break
+ else:
+ self.fail("No FK constraint for uniquetest_id found")
+
+ def test_m2m_repoint(self):
+ self._test_m2m_repoint(ManyToManyField)
+
+ def test_m2m_repoint_custom(self):
+ self._test_m2m_repoint(CustomManyToManyField)
+
+ def test_m2m_repoint_inherited(self):
+ self._test_m2m_repoint(InheritedManyToManyField)
@unittest.skipUnless(connection.features.supports_column_check_constraints, "No check constraints")
def test_check_constraints(self):
@@ -839,27 +877,19 @@ class SchemaTests(TransactionTestCase):
else:
self.fail("No check constraint for height found")
# Alter the column to remove it
+ old_field = Author._meta.get_field("height")
new_field = IntegerField(null=True, blank=True)
new_field.set_attributes_from_name("height")
with connection.schema_editor() as editor:
- editor.alter_field(
- Author,
- Author._meta.get_field("height"),
- new_field,
- strict=True,
- )
+ editor.alter_field(Author, old_field, new_field, strict=True)
constraints = self.get_constraints(Author._meta.db_table)
for name, details in constraints.items():
if details['columns'] == ["height"] and details['check']:
self.fail("Check constraint for height found")
# Alter the column to re-add it
+ new_field2 = Author._meta.get_field("height")
with connection.schema_editor() as editor:
- editor.alter_field(
- Author,
- new_field,
- Author._meta.get_field("height"),
- strict=True,
- )
+ editor.alter_field(Author, new_field, new_field2, strict=True)
constraints = self.get_constraints(Author._meta.db_table)
for name, details in constraints.items():
if details['columns'] == ["height"] and details['check']:
@@ -879,43 +909,29 @@ class SchemaTests(TransactionTestCase):
self.assertRaises(IntegrityError, Tag.objects.create, title="bar", slug="foo")
Tag.objects.all().delete()
# Alter the slug field to be non-unique
+ old_field = Tag._meta.get_field("slug")
new_field = SlugField(unique=False)
new_field.set_attributes_from_name("slug")
with connection.schema_editor() as editor:
- editor.alter_field(
- Tag,
- Tag._meta.get_field("slug"),
- new_field,
- strict=True,
- )
+ editor.alter_field(Tag, old_field, new_field, strict=True)
# Ensure the field is no longer unique
Tag.objects.create(title="foo", slug="foo")
Tag.objects.create(title="bar", slug="foo")
Tag.objects.all().delete()
# Alter the slug field to be unique
- new_new_field = SlugField(unique=True)
- new_new_field.set_attributes_from_name("slug")
- with connection.schema_editor() as editor:
- editor.alter_field(
- Tag,
- new_field,
- new_new_field,
- strict=True,
- )
+ new_field2 = SlugField(unique=True)
+ new_field2.set_attributes_from_name("slug")
+ with connection.schema_editor() as editor:
+ editor.alter_field(Tag, new_field, new_field2, strict=True)
# Ensure the field is unique again
Tag.objects.create(title="foo", slug="foo")
self.assertRaises(IntegrityError, Tag.objects.create, title="bar", slug="foo")
Tag.objects.all().delete()
# Rename the field
- new_field = SlugField(unique=False)
- new_field.set_attributes_from_name("slug2")
+ new_field3 = SlugField(unique=True)
+ new_field3.set_attributes_from_name("slug2")
with connection.schema_editor() as editor:
- editor.alter_field(
- Tag,
- Tag._meta.get_field("slug"),
- TagUniqueRename._meta.get_field("slug2"),
- strict=True,
- )
+ editor.alter_field(Tag, new_field2, new_field3, strict=True)
# Ensure the field is still unique
TagUniqueRename.objects.create(title="foo", slug2="foo")
self.assertRaises(IntegrityError, TagUniqueRename.objects.create, title="bar", slug2="foo")
@@ -936,24 +952,16 @@ class SchemaTests(TransactionTestCase):
UniqueTest.objects.all().delete()
# Alter the model to its non-unique-together companion
with connection.schema_editor() as editor:
- editor.alter_unique_together(
- UniqueTest,
- UniqueTest._meta.unique_together,
- [],
- )
+ editor.alter_unique_together(UniqueTest, UniqueTest._meta.unique_together, [])
# Ensure the fields are no longer unique
UniqueTest.objects.create(year=2012, slug="foo")
UniqueTest.objects.create(year=2012, slug="foo")
UniqueTest.objects.all().delete()
# Alter it back
- new_new_field = SlugField(unique=True)
- new_new_field.set_attributes_from_name("slug")
+ new_field2 = SlugField(unique=True)
+ new_field2.set_attributes_from_name("slug")
with connection.schema_editor() as editor:
- editor.alter_unique_together(
- UniqueTest,
- [],
- UniqueTest._meta.unique_together,
- )
+ editor.alter_unique_together(UniqueTest, [], UniqueTest._meta.unique_together)
# Ensure the fields are unique again
UniqueTest.objects.create(year=2012, slug="foo")
self.assertRaises(IntegrityError, UniqueTest.objects.create, year=2012, slug="foo")
@@ -977,11 +985,7 @@ class SchemaTests(TransactionTestCase):
)
# Alter the model to add an index
with connection.schema_editor() as editor:
- editor.alter_index_together(
- Tag,
- [],
- [("slug", "title")],
- )
+ editor.alter_index_together(Tag, [], [("slug", "title")])
# Ensure there is now an index
self.assertEqual(
True,
@@ -992,14 +996,10 @@ class SchemaTests(TransactionTestCase):
),
)
# Alter it back
- new_new_field = SlugField(unique=True)
- new_new_field.set_attributes_from_name("slug")
+ new_field2 = SlugField(unique=True)
+ new_field2.set_attributes_from_name("slug")
with connection.schema_editor() as editor:
- editor.alter_index_together(
- Tag,
- [("slug", "title")],
- [],
- )
+ editor.alter_index_together(Tag, [("slug", "title")], [])
# Ensure there's no index
self.assertEqual(
False,
@@ -1039,22 +1039,14 @@ class SchemaTests(TransactionTestCase):
self.assertEqual(columns['name'][0], "CharField")
# Alter the table
with connection.schema_editor() as editor:
- editor.alter_db_table(
- Author,
- "schema_author",
- "schema_otherauthor",
- )
+ editor.alter_db_table(Author, "schema_author", "schema_otherauthor")
# Ensure the table is there afterwards
Author._meta.db_table = "schema_otherauthor"
columns = self.column_classes(Author)
self.assertEqual(columns['name'][0], "CharField")
# Alter the table again
with connection.schema_editor() as editor:
- editor.alter_db_table(
- Author,
- "schema_otherauthor",
- "schema_author",
- )
+ editor.alter_db_table(Author, "schema_otherauthor", "schema_author")
# Ensure the table is still there
Author._meta.db_table = "schema_author"
columns = self.column_classes(Author)
@@ -1074,53 +1066,38 @@ class SchemaTests(TransactionTestCase):
self.get_indexes(Book._meta.db_table),
)
# Alter to remove the index
+ old_field = Book._meta.get_field("title")
new_field = CharField(max_length=100, db_index=False)
new_field.set_attributes_from_name("title")
with connection.schema_editor() as editor:
- editor.alter_field(
- Book,
- Book._meta.get_field("title"),
- new_field,
- strict=True,
- )
+ editor.alter_field(Book, old_field, new_field, strict=True)
# Ensure the table is there and has no index
self.assertNotIn(
"title",
self.get_indexes(Book._meta.db_table),
)
# Alter to re-add the index
+ new_field2 = Book._meta.get_field("title")
with connection.schema_editor() as editor:
- editor.alter_field(
- Book,
- new_field,
- Book._meta.get_field("title"),
- strict=True,
- )
+ editor.alter_field(Book, new_field, new_field2, strict=True)
# Ensure the table is there and has the index again
self.assertIn(
"title",
self.get_indexes(Book._meta.db_table),
)
# Add a unique column, verify that creates an implicit index
+ new_field3 = BookWithSlug._meta.get_field("slug")
with connection.schema_editor() as editor:
- editor.add_field(
- Book,
- BookWithSlug._meta.get_field("slug"),
- )
+ editor.add_field(Book, new_field3)
self.assertIn(
"slug",
self.get_indexes(Book._meta.db_table),
)
# Remove the unique, check the index goes with it
- new_field2 = CharField(max_length=20, unique=False)
- new_field2.set_attributes_from_name("slug")
+ new_field4 = CharField(max_length=20, unique=False)
+ new_field4.set_attributes_from_name("slug")
with connection.schema_editor() as editor:
- editor.alter_field(
- BookWithSlug,
- BookWithSlug._meta.get_field("slug"),
- new_field2,
- strict=True,
- )
+ editor.alter_field(BookWithSlug, new_field3, new_field4, strict=True)
self.assertNotIn(
"slug",
self.get_indexes(Book._meta.db_table),
@@ -1138,16 +1115,14 @@ class SchemaTests(TransactionTestCase):
self.get_indexes(Tag._meta.db_table)['id']['primary_key'],
)
# Alter to change the PK
+ id_field = Tag._meta.get_field("id")
+ old_field = Tag._meta.get_field("slug")
new_field = SlugField(primary_key=True)
new_field.set_attributes_from_name("slug")
new_field.model = Tag
with connection.schema_editor() as editor:
- editor.remove_field(Tag, Tag._meta.get_field("id"))
- editor.alter_field(
- Tag,
- Tag._meta.get_field("slug"),
- new_field,
- )
+ editor.remove_field(Tag, id_field)
+ editor.alter_field(Tag, old_field, new_field)
# Ensure the PK changed
self.assertNotIn(
'id',
@@ -1203,10 +1178,7 @@ class SchemaTests(TransactionTestCase):
new_field = ForeignKey(AuthorWithEvenLongerName, related_name="something")
new_field.set_attributes_from_name("author_other_really_long_named_i_mean_so_long_fk")
with connection.schema_editor() as editor:
- editor.add_field(
- BookWithLongName,
- new_field,
- )
+ editor.add_field(BookWithLongName, new_field)
def test_creation_deletion_reserved_names(self):
"""
@@ -1305,50 +1277,6 @@ class SchemaTests(TransactionTestCase):
item = cursor.fetchall()[0]
self.assertEqual(item[0], None if connection.features.interprets_empty_strings_as_nulls else '')
- def test_custom_manytomanyfield(self):
- """
- #24104 - Schema editors should look for many_to_many
- """
- # Create the tables
- with connection.schema_editor() as editor:
- editor.create_model(AuthorWithM2M)
- editor.create_model(TagM2MTest)
- # Create an M2M field
- new_field = CustomManyToManyField("schema.TagM2MTest", related_name="authors")
- new_field.contribute_to_class(AuthorWithM2M, "tags")
- # Ensure there's no m2m table there
- self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through)
- try:
- # Add the field
- with connection.schema_editor() as editor:
- editor.add_field(
- AuthorWithM2M,
- new_field,
- )
- # Ensure there is now an m2m table there
- columns = self.column_classes(new_field.rel.through)
- self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField")
-
- # "Alter" the field. This should not rename the DB table to itself.
- with connection.schema_editor() as editor:
- editor.alter_field(
- AuthorWithM2M,
- new_field,
- new_field,
- )
-
- # Remove the M2M table again
- with connection.schema_editor() as editor:
- editor.remove_field(
- AuthorWithM2M,
- new_field,
- )
- # Ensure there's no m2m table there
- self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through)
- finally:
- # Cleanup model states
- AuthorWithM2M._meta.local_many_to_many.remove(new_field)
-
def test_add_field_default_dropped(self):
# Create the table
with connection.schema_editor() as editor: