Commit b25aee3b authored by Andrew Godwin's avatar Andrew Godwin
Browse files

Fixed #22476: Couldn't alter attributes on M2Ms with through= set

parent 5ea34f3f
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -483,8 +483,11 @@ class BaseDatabaseSchemaEditor(object):
        new_type = new_db_params['type']
        if old_type is None and new_type is None and (old_field.rel.through and new_field.rel.through and old_field.rel.through._meta.auto_created and new_field.rel.through._meta.auto_created):
            return self._alter_many_to_many(model, old_field, new_field, strict)
        elif old_type is None and new_type is None and (old_field.rel.through and new_field.rel.through and not old_field.rel.through._meta.auto_created and not new_field.rel.through._meta.auto_created):
            # Both sides have through models; this is a no-op.
            return
        elif old_type is None or new_type is None:
            raise ValueError("Cannot alter field %s into %s - they are not compatible types (probably means only one is an M2M with implicit through model)" % (
            raise ValueError("Cannot alter field %s into %s - they are not compatible types (you cannot alter to or from M2M fields, or add or remove through= on M2M fields)" % (
                old_field,
                new_field,
            ))
+4 −1
Original line number Diff line number Diff line
@@ -148,8 +148,11 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
        new_type = new_db_params['type']
        if old_type is None and new_type is None and (old_field.rel.through and new_field.rel.through and old_field.rel.through._meta.auto_created and new_field.rel.through._meta.auto_created):
            return self._alter_many_to_many(model, old_field, new_field, strict)
        elif old_type is None and new_type is None and (old_field.rel.through and new_field.rel.through and not old_field.rel.through._meta.auto_created and not new_field.rel.through._meta.auto_created):
            # Both sides have through models; this is a no-op.
            return
        elif old_type is None or new_type is None:
            raise ValueError("Cannot alter field %s into %s - they are not compatible types (probably means only one is an M2M with implicit through model)" % (
            raise ValueError("Cannot alter field %s into %s - they are not compatible types (you cannot alter to or from M2M fields, or add or remove through= on M2M fields)" % (
                old_field,
                new_field,
            ))
+16 −0
Original line number Diff line number Diff line
@@ -24,6 +24,22 @@ class AuthorWithM2M(models.Model):
        apps = new_apps


class AuthorWithM2MThrough(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")

    class Meta:
        apps = new_apps


class Book(models.Model):
    author = models.ForeignKey(Author)
    title = models.CharField(max_length=100, db_index=True)
+25 −1
Original line number Diff line number Diff line
@@ -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, TagThrough, BookWithM2MThrough)
    UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough)


class SchemaTests(TransactionTestCase):
@@ -402,6 +402,30 @@ class SchemaTests(TransactionTestCase):
            # Cleanup model states
            AuthorWithM2M._meta.local_many_to_many.remove(new_field)

    def test_m2m_through_alter(self):
        """
        Tests altering M2Ms with explicit through models (should no-op)
        """
        # Create the tables
        with connection.schema_editor() as editor:
            editor.create_model(AuthorTag)
            editor.create_model(AuthorWithM2MThrough)
            editor.create_model(TagM2MTest)
        # Ensure the m2m table is there
        self.assertEqual(len(self.column_classes(AuthorTag)), 3)
        # "Alter" the field's blankness. This should not actually do anything.
        with connection.schema_editor() as editor:
            old_field = AuthorWithM2MThrough._meta.get_field_by_name("tags")[0]
            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,
            )
        # Ensure the m2m table is still there
        self.assertEqual(len(self.column_classes(AuthorTag)), 3)

    def test_m2m_repoint(self):
        """
        Tests repointing M2M fields