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

Thanks Markus Holtermann for review.
This commit is contained in:
Tim Graham 2017-03-17 11:25:12 -04:00
parent 4224ecb74e
commit 3d19d1428a
6 changed files with 74 additions and 7 deletions

View File

@ -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

View File

@ -293,9 +293,11 @@ class ModelBase(type):
else:
new_class.add_to_class(field.name, copy.deepcopy(field))
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.
# 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)

View File

@ -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):
"""

View File

@ -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):

View File

@ -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

View File

@ -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'])