Browse Source

Fixed #27915 -- Allowed Meta.indexes to be defined in abstract models.

Thanks Markus Holtermann for review.
Tim Graham 8 years ago
parent
commit
3d19d1428a

+ 5 - 0
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

+ 8 - 6
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

+ 5 - 0
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):
         """

+ 27 - 0
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):
 

+ 16 - 0
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

+ 13 - 1
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'])