summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2017-03-17 11:25:12 -0400
committerTim Graham <timograham@gmail.com>2017-03-21 11:32:43 -0400
commite5880516f90773d653a67e95b9b942229e8e9021 (patch)
tree8dfdd4811281cae1c75e62a372eb0453796e08cc
parent5310106ee3c1f5403ceeca9ee09fc92e5e353fa5 (diff)
[1.11.x] Fixed #27915 -- Allowed Meta.indexes to be defined in abstract models.
Thanks Markus Holtermann for review. Backport of 3d19d1428a05b514afb771b52870d1f7c25670d1 from master
-rw-r--r--django/db/migrations/state.py5
-rw-r--r--django/db/models/base.py14
-rw-r--r--django/db/models/indexes.py5
-rw-r--r--tests/migrations/test_state.py27
-rw-r--r--tests/model_indexes/models.py16
-rw-r--r--tests/model_indexes/tests.py14
6 files changed, 74 insertions, 7 deletions
diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py
index e44f737106..b6540f5f10 100644
--- a/django/db/migrations/state.py
+++ b/django/db/migrations/state.py
@@ -460,6 +460,11 @@ class ModelState(object):
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]
# Force-convert all options to text_type (#23226)
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 53b761f45a..217bde1e2a 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -303,12 +303,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 1ef5a4ab50..0ee2bf3610 100644
--- a/django/db/models/indexes.py
+++ b/django/db/models/indexes.py
@@ -77,6 +77,11 @@ class Index(object):
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 c8eb1726ba..68c3b98391 100644
--- a/tests/migrations/test_state.py
+++ b/tests/migrations/test_state.py
@@ -1044,6 +1044,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'])