Add more stringent M2M tests and fix the bug they exposed
This commit is contained in:
parent
5b522cd85a
commit
52edc16086
|
@ -9,7 +9,7 @@ from django.conf import settings
|
||||||
from django.db.models.fields.related import ManyToManyRel
|
from django.db.models.fields.related import ManyToManyRel
|
||||||
from django.db.models.fields import AutoField, FieldDoesNotExist
|
from django.db.models.fields import AutoField, FieldDoesNotExist
|
||||||
from django.db.models.fields.proxy import OrderWrt
|
from django.db.models.fields.proxy import OrderWrt
|
||||||
from django.db.models.loading import get_models, app_cache_ready, cache
|
from django.db.models.loading import app_cache_ready, cache
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.encoding import force_text, smart_text, python_2_unicode_compatible
|
from django.utils.encoding import force_text, smart_text, python_2_unicode_compatible
|
||||||
|
@ -495,7 +495,7 @@ class Options(object):
|
||||||
cache[obj] = model
|
cache[obj] = model
|
||||||
# Collect also objects which are in relation to some proxy child/parent of self.
|
# Collect also objects which are in relation to some proxy child/parent of self.
|
||||||
proxy_cache = cache.copy()
|
proxy_cache = cache.copy()
|
||||||
for klass in get_models(include_auto_created=True, only_installed=False):
|
for klass in self.app_cache.get_models(include_auto_created=True, only_installed=False):
|
||||||
if not klass._meta.swapped:
|
if not klass._meta.swapped:
|
||||||
for f in klass._meta.local_fields:
|
for f in klass._meta.local_fields:
|
||||||
if f.rel and not isinstance(f.rel.to, six.string_types) and f.generate_reverse_relation:
|
if f.rel and not isinstance(f.rel.to, six.string_types) and f.generate_reverse_relation:
|
||||||
|
@ -538,7 +538,7 @@ class Options(object):
|
||||||
cache[obj] = parent
|
cache[obj] = parent
|
||||||
else:
|
else:
|
||||||
cache[obj] = model
|
cache[obj] = model
|
||||||
for klass in get_models(only_installed=False):
|
for klass in self.app_cache.get_models(only_installed=False):
|
||||||
if not klass._meta.swapped:
|
if not klass._meta.swapped:
|
||||||
for f in klass._meta.local_many_to_many:
|
for f in klass._meta.local_many_to_many:
|
||||||
if (f.rel
|
if (f.rel
|
||||||
|
|
|
@ -116,7 +116,7 @@ class OperationTests(MigrationTestBase):
|
||||||
"""
|
"""
|
||||||
project_state = self.set_up_test_model("test_adflmm", second_model=True)
|
project_state = self.set_up_test_model("test_adflmm", second_model=True)
|
||||||
# Test the state alteration
|
# Test the state alteration
|
||||||
operation = migrations.AddField("Pony", "stables", models.ManyToManyField("Stable"))
|
operation = migrations.AddField("Pony", "stables", models.ManyToManyField("Stable", related_name="ponies"))
|
||||||
new_state = project_state.clone()
|
new_state = project_state.clone()
|
||||||
operation.state_forwards("test_adflmm", new_state)
|
operation.state_forwards("test_adflmm", new_state)
|
||||||
self.assertEqual(len(new_state.models["test_adflmm", "pony"].fields), 4)
|
self.assertEqual(len(new_state.models["test_adflmm", "pony"].fields), 4)
|
||||||
|
@ -126,6 +126,13 @@ class OperationTests(MigrationTestBase):
|
||||||
operation.database_forwards("test_adflmm", editor, project_state, new_state)
|
operation.database_forwards("test_adflmm", editor, project_state, new_state)
|
||||||
self.assertTableExists("test_adflmm_pony_stables")
|
self.assertTableExists("test_adflmm_pony_stables")
|
||||||
self.assertColumnNotExists("test_adflmm_pony", "stables")
|
self.assertColumnNotExists("test_adflmm_pony", "stables")
|
||||||
|
# Make sure the M2M field actually works
|
||||||
|
app_cache = new_state.render()
|
||||||
|
Pony = app_cache.get_model("test_adflmm", "Pony")
|
||||||
|
p = Pony.objects.create(pink=False, weight=4.55)
|
||||||
|
p.stables.create()
|
||||||
|
self.assertEqual(p.stables.count(), 1)
|
||||||
|
p.stables.all().delete()
|
||||||
# And test reversal
|
# And test reversal
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
operation.database_backwards("test_adflmm", editor, new_state, project_state)
|
operation.database_backwards("test_adflmm", editor, new_state, project_state)
|
||||||
|
|
|
@ -37,7 +37,7 @@ class BookWithM2M(models.Model):
|
||||||
author = models.ForeignKey(Author)
|
author = models.ForeignKey(Author)
|
||||||
title = models.CharField(max_length=100, db_index=True)
|
title = models.CharField(max_length=100, db_index=True)
|
||||||
pub_date = models.DateTimeField()
|
pub_date = models.DateTimeField()
|
||||||
tags = models.ManyToManyField("Tag", related_name="books")
|
tags = models.ManyToManyField("TagM2MTest", related_name="books")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_cache = new_app_cache
|
app_cache = new_app_cache
|
||||||
|
@ -62,6 +62,14 @@ class Tag(models.Model):
|
||||||
app_cache = new_app_cache
|
app_cache = new_app_cache
|
||||||
|
|
||||||
|
|
||||||
|
class TagM2MTest(models.Model):
|
||||||
|
title = models.CharField(max_length=255)
|
||||||
|
slug = models.SlugField(unique=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
app_cache = new_app_cache
|
||||||
|
|
||||||
|
|
||||||
class TagIndexed(models.Model):
|
class TagIndexed(models.Model):
|
||||||
title = models.CharField(max_length=255)
|
title = models.CharField(max_length=255)
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.db import connection, DatabaseError, IntegrityError
|
||||||
from django.db.models.fields import IntegerField, TextField, CharField, SlugField
|
from django.db.models.fields import IntegerField, TextField, CharField, SlugField
|
||||||
from django.db.models.fields.related import ManyToManyField, ForeignKey
|
from django.db.models.fields.related import ManyToManyField, ForeignKey
|
||||||
from django.db.transaction import atomic
|
from django.db.transaction import atomic
|
||||||
from .models import Author, AuthorWithM2M, Book, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagUniqueRename, UniqueTest
|
from .models import Author, AuthorWithM2M, Book, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest
|
||||||
|
|
||||||
|
|
||||||
class SchemaTests(TransactionTestCase):
|
class SchemaTests(TransactionTestCase):
|
||||||
|
@ -20,7 +20,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
|
|
||||||
available_apps = []
|
available_apps = []
|
||||||
|
|
||||||
models = [Author, AuthorWithM2M, Book, BookWithSlug, BookWithM2M, Tag, TagUniqueRename, UniqueTest]
|
models = [Author, AuthorWithM2M, Book, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest]
|
||||||
no_table_strings = ["no such table", "unknown table", "does not exist"]
|
no_table_strings = ["no such table", "unknown table", "does not exist"]
|
||||||
|
|
||||||
# Utility functions
|
# Utility functions
|
||||||
|
@ -234,7 +234,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
editor.create_model(BookWithM2M)
|
editor.create_model(BookWithM2M)
|
||||||
# Ensure there is now an m2m table there
|
# Ensure there is now an m2m table there
|
||||||
columns = self.column_classes(BookWithM2M._meta.get_field_by_name("tags")[0].rel.through)
|
columns = self.column_classes(BookWithM2M._meta.get_field_by_name("tags")[0].rel.through)
|
||||||
self.assertEqual(columns['tag_id'][0], "IntegerField")
|
self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField")
|
||||||
|
|
||||||
def test_m2m(self):
|
def test_m2m(self):
|
||||||
"""
|
"""
|
||||||
|
@ -243,9 +243,9 @@ class SchemaTests(TransactionTestCase):
|
||||||
# Create the tables
|
# Create the tables
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.create_model(AuthorWithM2M)
|
editor.create_model(AuthorWithM2M)
|
||||||
editor.create_model(Tag)
|
editor.create_model(TagM2MTest)
|
||||||
# Create an M2M field
|
# Create an M2M field
|
||||||
new_field = ManyToManyField("schema.Tag", related_name="authors")
|
new_field = ManyToManyField("schema.TagM2MTest", related_name="authors")
|
||||||
new_field.contribute_to_class(AuthorWithM2M, "tags")
|
new_field.contribute_to_class(AuthorWithM2M, "tags")
|
||||||
try:
|
try:
|
||||||
# Ensure there's no m2m table there
|
# Ensure there's no m2m table there
|
||||||
|
@ -258,7 +258,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
)
|
)
|
||||||
# Ensure there is now an m2m table there
|
# Ensure there is now an m2m table there
|
||||||
columns = self.column_classes(new_field.rel.through)
|
columns = self.column_classes(new_field.rel.through)
|
||||||
self.assertEqual(columns['tag_id'][0], "IntegerField")
|
self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField")
|
||||||
# Remove the M2M table again
|
# Remove the M2M table again
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.remove_field(
|
editor.remove_field(
|
||||||
|
@ -279,17 +279,17 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
editor.create_model(Author)
|
editor.create_model(Author)
|
||||||
editor.create_model(BookWithM2M)
|
editor.create_model(BookWithM2M)
|
||||||
editor.create_model(Tag)
|
editor.create_model(TagM2MTest)
|
||||||
editor.create_model(UniqueTest)
|
editor.create_model(UniqueTest)
|
||||||
# Ensure the M2M exists and points to Tag
|
# Ensure the M2M exists and points to TagM2MTest
|
||||||
constraints = connection.introspection.get_constraints(connection.cursor(), BookWithM2M._meta.get_field_by_name("tags")[0].rel.through._meta.db_table)
|
constraints = connection.introspection.get_constraints(connection.cursor(), BookWithM2M._meta.get_field_by_name("tags")[0].rel.through._meta.db_table)
|
||||||
if connection.features.supports_foreign_keys:
|
if connection.features.supports_foreign_keys:
|
||||||
for name, details in constraints.items():
|
for name, details in constraints.items():
|
||||||
if details['columns'] == ["tag_id"] and details['foreign_key']:
|
if details['columns'] == ["tagm2mtest_id"] and details['foreign_key']:
|
||||||
self.assertEqual(details['foreign_key'], ('schema_tag', 'id'))
|
self.assertEqual(details['foreign_key'], ('schema_tagm2mtest', 'id'))
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
self.fail("No FK constraint for tag_id found")
|
self.fail("No FK constraint for tagm2mtest_id found")
|
||||||
# Repoint the M2M
|
# Repoint the M2M
|
||||||
new_field = ManyToManyField(UniqueTest)
|
new_field = ManyToManyField(UniqueTest)
|
||||||
new_field.contribute_to_class(BookWithM2M, "uniques")
|
new_field.contribute_to_class(BookWithM2M, "uniques")
|
||||||
|
@ -310,7 +310,7 @@ class SchemaTests(TransactionTestCase):
|
||||||
self.assertEqual(details['foreign_key'], ('schema_uniquetest', 'id'))
|
self.assertEqual(details['foreign_key'], ('schema_uniquetest', 'id'))
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
self.fail("No FK constraint for tag_id found")
|
self.fail("No FK constraint for uniquetest_id found")
|
||||||
finally:
|
finally:
|
||||||
# Cleanup model states
|
# Cleanup model states
|
||||||
BookWithM2M._meta.local_many_to_many.remove(new_field)
|
BookWithM2M._meta.local_many_to_many.remove(new_field)
|
||||||
|
|
Loading…
Reference in New Issue