Add more stringent M2M tests and fix the bug they exposed

This commit is contained in:
Andrew Godwin 2013-08-19 13:50:26 +01:00
parent 5b522cd85a
commit 52edc16086
4 changed files with 32 additions and 17 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)