diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py index e47e1d0e8e..5c30c4c8a1 100644 --- a/django/db/migrations/state.py +++ b/django/db/migrations/state.py @@ -443,6 +443,11 @@ class ModelState: elif name == "index_together": it = model._meta.original_attrs["index_together"] options[name] = set(normalize_together(it)) + elif name == "indexes": + indexes = [idx.clone() for idx in model._meta.indexes] + for index in indexes: + index.set_name_with_model(model) + options['indexes'] = indexes else: options[name] = model._meta.original_attrs[name] # If we're ignoring relationships, remove all field-listing model diff --git a/django/db/models/base.py b/django/db/models/base.py index 03375911d4..3c1cb9a119 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -293,12 +293,14 @@ class ModelBase(type): else: new_class.add_to_class(field.name, copy.deepcopy(field)) - # Set the name of _meta.indexes. This can't be done in - # Options.contribute_to_class() because fields haven't been added to - # the model at that point. - for index in new_class._meta.indexes: - if not index.name: - index.set_name_with_model(new_class) + if base_meta and base_meta.abstract and not abstract: + new_class._meta.indexes = [copy.deepcopy(idx) for idx in new_class._meta.indexes] + # Set the name of _meta.indexes. This can't be done in + # Options.contribute_to_class() because fields haven't been added + # to the model at that point. + for index in new_class._meta.indexes: + if not index.name: + index.set_name_with_model(new_class) if abstract: # Abstract base models can't be instantiated and don't appear in diff --git a/django/db/models/indexes.py b/django/db/models/indexes.py index 3bb73cf16a..7e5b8bca30 100644 --- a/django/db/models/indexes.py +++ b/django/db/models/indexes.py @@ -75,6 +75,11 @@ class Index: path = path.replace('django.db.models.indexes', 'django.db.models') return (path, (), {'fields': self.fields, 'name': self.name}) + def clone(self): + """Create a copy of this Index.""" + path, args, kwargs = self.deconstruct() + return self.__class__(*args, **kwargs) + @staticmethod def _hash_generator(*args): """ diff --git a/tests/migrations/test_state.py b/tests/migrations/test_state.py index fe9c4fd03a..2029077256 100644 --- a/tests/migrations/test_state.py +++ b/tests/migrations/test_state.py @@ -1043,6 +1043,33 @@ class ModelStateTests(SimpleTestCase): state = ModelState.from_model(PrivateFieldModel) self.assertNotIn('order_with_respect_to', state.options) + @isolate_apps('migrations') + def test_abstract_model_children_inherit_indexes(self): + class Abstract(models.Model): + name = models.CharField(max_length=50) + + class Meta: + app_label = 'migrations' + abstract = True + indexes = [models.indexes.Index(fields=['name'])] + + class Child1(Abstract): + pass + + class Child2(Abstract): + pass + + child1_state = ModelState.from_model(Child1) + child2_state = ModelState.from_model(Child2) + index_names = [index.name for index in child1_state.options['indexes']] + self.assertEqual(index_names, ['migrations__name_b0afd7_idx']) + index_names = [index.name for index in child2_state.options['indexes']] + self.assertEqual(index_names, ['migrations__name_016466_idx']) + + # Modifying the state doesn't modify the index on the model. + child1_state.options['indexes'][0].name = 'bar' + self.assertEqual(Child1._meta.indexes[0].name, 'migrations__name_b0afd7_idx') + class RelatedModelsTests(SimpleTestCase): diff --git a/tests/model_indexes/models.py b/tests/model_indexes/models.py index 598a4cb808..34b3f3246c 100644 --- a/tests/model_indexes/models.py +++ b/tests/model_indexes/models.py @@ -5,3 +5,19 @@ class Book(models.Model): title = models.CharField(max_length=50) author = models.CharField(max_length=50) pages = models.IntegerField(db_column='page_count') + + +class AbstractModel(models.Model): + name = models.CharField(max_length=50) + + class Meta: + abstract = True + indexes = [models.indexes.Index(fields=['name'])] + + +class ChildModel1(AbstractModel): + pass + + +class ChildModel2(AbstractModel): + pass diff --git a/tests/model_indexes/tests.py b/tests/model_indexes/tests.py index 33e4bfaa7c..791233daf0 100644 --- a/tests/model_indexes/tests.py +++ b/tests/model_indexes/tests.py @@ -1,7 +1,7 @@ from django.db import models from django.test import SimpleTestCase -from .models import Book +from .models import Book, ChildModel1, ChildModel2 class IndexesTests(SimpleTestCase): @@ -76,3 +76,15 @@ class IndexesTests(SimpleTestCase): self.assertEqual(path, 'django.db.models.Index') self.assertEqual(args, ()) self.assertEqual(kwargs, {'fields': ['title'], 'name': 'model_index_title_196f42_idx'}) + + def test_clone(self): + index = models.Index(fields=['title']) + new_index = index.clone() + self.assertIsNot(index, new_index) + self.assertEqual(index.fields, new_index.fields) + + def test_abstract_children(self): + index_names = [index.name for index in ChildModel1._meta.indexes] + self.assertEqual(index_names, ['model_index_name_440998_idx']) + index_names = [index.name for index in ChildModel2._meta.indexes] + self.assertEqual(index_names, ['model_index_name_b6c374_idx'])