Commit 52edc160 authored by Andrew Godwin's avatar Andrew Godwin
Browse files

Add more stringent M2M tests and fix the bug they exposed

parent 5b522cd8
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ from django.conf import settings
from django.db.models.fields.related import ManyToManyRel
from django.db.models.fields import AutoField, FieldDoesNotExist
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.functional import cached_property
from django.utils.encoding import force_text, smart_text, python_2_unicode_compatible
@@ -495,7 +495,7 @@ class Options(object):
                    cache[obj] = model
        # Collect also objects which are in relation to some proxy child/parent of self.
        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:
                for f in klass._meta.local_fields:
                    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
                else:
                    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:
                for f in klass._meta.local_many_to_many:
                    if (f.rel
+8 −1
Original line number Diff line number Diff line
@@ -116,7 +116,7 @@ class OperationTests(MigrationTestBase):
        """
        project_state = self.set_up_test_model("test_adflmm", second_model=True)
        # 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()
        operation.state_forwards("test_adflmm", new_state)
        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)
        self.assertTableExists("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
        with connection.schema_editor() as editor:
            operation.database_backwards("test_adflmm", editor, new_state, project_state)
+9 −1
Original line number Diff line number Diff line
@@ -37,7 +37,7 @@ class BookWithM2M(models.Model):
    author = models.ForeignKey(Author)
    title = models.CharField(max_length=100, db_index=True)
    pub_date = models.DateTimeField()
    tags = models.ManyToManyField("Tag", related_name="books")
    tags = models.ManyToManyField("TagM2MTest", related_name="books")

    class Meta:
        app_cache = new_app_cache
@@ -62,6 +62,14 @@ class Tag(models.Model):
        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):
    title = models.CharField(max_length=255)
    slug = models.SlugField(unique=True)
+12 −12
Original line number Diff line number Diff line
@@ -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.related import ManyToManyField, ForeignKey
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):
@@ -20,7 +20,7 @@ class SchemaTests(TransactionTestCase):
    
    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"]

    # Utility functions
@@ -234,7 +234,7 @@ class SchemaTests(TransactionTestCase):
            editor.create_model(BookWithM2M)
        # Ensure there is now an m2m table there
        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):
        """
@@ -243,9 +243,9 @@ class SchemaTests(TransactionTestCase):
        # Create the tables
        with connection.schema_editor() as editor:
            editor.create_model(AuthorWithM2M)
            editor.create_model(Tag)
            editor.create_model(TagM2MTest)
        # 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")
        try:
            # Ensure there's no m2m table there
@@ -258,7 +258,7 @@ class SchemaTests(TransactionTestCase):
                )
            # Ensure there is now an m2m table there
            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
            with connection.schema_editor() as editor:
                editor.remove_field(
@@ -279,17 +279,17 @@ class SchemaTests(TransactionTestCase):
        with connection.schema_editor() as editor:
            editor.create_model(Author)
            editor.create_model(BookWithM2M)
            editor.create_model(Tag)
            editor.create_model(TagM2MTest)
            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)
        if connection.features.supports_foreign_keys:
            for name, details in constraints.items():
                if details['columns'] == ["tag_id"] and details['foreign_key']:
                    self.assertEqual(details['foreign_key'], ('schema_tag', 'id'))
                if details['columns'] == ["tagm2mtest_id"] and details['foreign_key']:
                    self.assertEqual(details['foreign_key'], ('schema_tagm2mtest', 'id'))
                    break
            else:
                self.fail("No FK constraint for tag_id found")
                self.fail("No FK constraint for tagm2mtest_id found")
        # Repoint the M2M
        new_field = ManyToManyField(UniqueTest)
        new_field.contribute_to_class(BookWithM2M, "uniques")
@@ -310,7 +310,7 @@ class SchemaTests(TransactionTestCase):
                        self.assertEqual(details['foreign_key'], ('schema_uniquetest', 'id'))
                        break
                else:
                    self.fail("No FK constraint for tag_id found")
                    self.fail("No FK constraint for uniquetest_id found")
        finally:
            # Cleanup model states
            BookWithM2M._meta.local_many_to_many.remove(new_field)