From 6b078044745ee3c665051a886021c6fd1f6873b6 Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Sat, 8 Mar 2014 15:57:25 -0800 Subject: [PATCH] Fixed #22183: Through M2Ms now correctly handled --- django/db/backends/schema.py | 3 ++- django/db/models/fields/related.py | 5 +++++ tests/field_deconstruction/tests.py | 6 ++++++ tests/schema/models.py | 15 +++++++++++++++ tests/schema/tests.py | 18 ++++++++++++++++-- 5 files changed, 44 insertions(+), 3 deletions(-) diff --git a/django/db/backends/schema.py b/django/db/backends/schema.py index 862b22f901..ef53ac1608 100644 --- a/django/db/backends/schema.py +++ b/django/db/backends/schema.py @@ -262,7 +262,8 @@ class BaseDatabaseSchemaEditor(object): }) # Make M2M tables for field in model._meta.local_many_to_many: - self.create_model(field.rel.through) + if field.rel.through._meta.auto_created: + self.create_model(field.rel.through) def delete_model(self, model): """ diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index e4c055ff63..41c020f509 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -2032,6 +2032,11 @@ class ManyToManyField(RelatedField): kwargs['to'] = self.rel.to else: kwargs['to'] = "%s.%s" % (self.rel.to._meta.app_label, self.rel.to._meta.object_name) + if getattr(self.rel, 'through', None) is not None: + if isinstance(self.rel.through, six.string_types): + kwargs['through'] = self.rel.through + else: + kwargs['through'] = "%s.%s" % (self.rel.through._meta.app_label, self.rel.through._meta.object_name) # If swappable is True, then see if we're actually pointing to the target # of a swap. swappable_setting = self.swappable_setting diff --git a/tests/field_deconstruction/tests.py b/tests/field_deconstruction/tests.py index 0d24c2a268..26ceb4e0cf 100644 --- a/tests/field_deconstruction/tests.py +++ b/tests/field_deconstruction/tests.py @@ -242,6 +242,12 @@ class FieldDeconstructionTests(TestCase): self.assertEqual(args, []) self.assertEqual(kwargs, {"to": "auth.User"}) self.assertEqual(kwargs['to'].setting_name, "AUTH_USER_MODEL") + # Test through + field = models.ManyToManyField("auth.Permission", through="auth.Group") + name, path, args, kwargs = field.deconstruct() + self.assertEqual(path, "django.db.models.ManyToManyField") + self.assertEqual(args, []) + self.assertEqual(kwargs, {"to": "auth.Permission", "through": "auth.Group"}) @override_settings(AUTH_USER_MODEL="auth.Permission") def test_many_to_many_field_swapped(self): diff --git a/tests/schema/models.py b/tests/schema/models.py index 67cfcf6a89..f85a3c6362 100644 --- a/tests/schema/models.py +++ b/tests/schema/models.py @@ -44,6 +44,21 @@ class BookWithM2M(models.Model): 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 + + class BookWithSlug(models.Model): author = models.ForeignKey(Author) title = models.CharField(max_length=100, db_index=True) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index a502ef6002..3eb826cee4 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -9,7 +9,7 @@ from django.db.models.fields.related import ManyToManyField, ForeignKey from django.db.transaction import atomic from .models import (Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, - UniqueTest, Thing) + UniqueTest, Thing, TagThrough, BookWithM2MThrough) class SchemaTests(TransactionTestCase): @@ -26,7 +26,7 @@ class SchemaTests(TransactionTestCase): models = [ Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest, - Thing + Thing, TagThrough, BookWithM2MThrough ] # Utility functions @@ -310,6 +310,20 @@ class SchemaTests(TransactionTestCase): columns = self.column_classes(BookWithM2M._meta.get_field_by_name("tags")[0].rel.through) self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField") + def test_m2m_create_through(self): + """ + Tests M2M fields on models during creation with through models + """ + # Create the tables + with connection.schema_editor() as editor: + editor.create_model(TagThrough) + editor.create_model(TagM2MTest) + editor.create_model(BookWithM2MThrough) + # Ensure there is now an m2m table there + columns = self.column_classes(TagThrough) + self.assertEqual(columns['book_id'][0], "IntegerField") + self.assertEqual(columns['tag_id'][0], "IntegerField") + def test_m2m(self): """ Tests adding/removing M2M fields on models