summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorAkshesh <aksheshdoshi@gmail.com>2016-06-20 21:20:05 +0530
committerTim Graham <timograham@gmail.com>2016-08-05 18:12:51 -0400
commit6a8372e6ec42eb50e05f9dbcb26b783237bdc233 (patch)
treeb0ea6c93f189009bf54bead1a7d665c27f3c6744 /django
parentd117567c7d65c3c28858c4dfc771483b182075e4 (diff)
Fixed #26808 -- Added Meta.indexes for class-based indexes.
* Added the index name to its deconstruction. * Added indexes to sqlite3.schema._remake_table() so that indexes aren't dropped when _remake_table() is called. Thanks timgraham & MarkusH for review and advice.
Diffstat (limited to 'django')
-rw-r--r--django/db/backends/base/schema.py7
-rw-r--r--django/db/backends/sqlite3/schema.py8
-rw-r--r--django/db/migrations/autodetector.py60
-rw-r--r--django/db/migrations/state.py7
-rw-r--r--django/db/models/base.py7
-rw-r--r--django/db/models/indexes.py2
-rw-r--r--django/db/models/options.py1
7 files changed, 89 insertions, 3 deletions
diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py
index b936b61fb8..39a7e5e623 100644
--- a/django/db/backends/base/schema.py
+++ b/django/db/backends/base/schema.py
@@ -889,8 +889,8 @@ class BaseDatabaseSchemaEditor(object):
def _model_indexes_sql(self, model):
"""
- Return all index SQL statements (field indexes, index_together) for the
- specified model, as a list.
+ Return all index SQL statements (field indexes, index_together,
+ Meta.indexes) for the specified model, as a list.
"""
if not model._meta.managed or model._meta.proxy or model._meta.swapped:
return []
@@ -901,6 +901,9 @@ class BaseDatabaseSchemaEditor(object):
for field_names in model._meta.index_together:
fields = [model._meta.get_field(field) for field in field_names]
output.append(self._create_index_sql(model, fields, suffix="_idx"))
+
+ for index in model._meta.indexes:
+ output.append(index.create_sql(model, self))
return output
def _field_indexes_sql(self, model, field):
diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py
index 1a9fb1523a..36adb22584 100644
--- a/django/db/backends/sqlite3/schema.py
+++ b/django/db/backends/sqlite3/schema.py
@@ -156,12 +156,20 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
for index in model._meta.index_together
]
+ indexes = model._meta.indexes
+ if delete_field:
+ indexes = [
+ index for index in indexes
+ if delete_field.name not in index.fields
+ ]
+
# Construct a new model for the new state
meta_contents = {
'app_label': model._meta.app_label,
'db_table': model._meta.db_table,
'unique_together': unique_together,
'index_together': index_together,
+ 'indexes': indexes,
'apps': apps,
}
meta = type("Meta", tuple(), meta_contents)
diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py
index 3c82473fe1..a0146710e6 100644
--- a/django/db/migrations/autodetector.py
+++ b/django/db/migrations/autodetector.py
@@ -126,6 +126,7 @@ class MigrationAutodetector(object):
# We'll then go through that list later and order it and split
# into migrations to resolve dependencies caused by M2Ms and FKs.
self.generated_operations = {}
+ self.altered_indexes = {}
# Prepare some old/new state and model lists, separating
# proxy models and ignoring unmigrated apps.
@@ -175,6 +176,12 @@ class MigrationAutodetector(object):
self.generate_altered_options()
self.generate_altered_managers()
+ # Create the altered indexes and store them in self.altered_indexes.
+ # This avoids the same computation in generate_removed_indexes()
+ # and generate_added_indexes().
+ self.create_altered_indexes()
+ # Generate index removal operations before field is removed
+ self.generate_removed_indexes()
# Generate field operations
self.generate_renamed_fields()
self.generate_removed_fields()
@@ -182,6 +189,7 @@ class MigrationAutodetector(object):
self.generate_altered_fields()
self.generate_altered_unique_together()
self.generate_altered_index_together()
+ self.generate_added_indexes()
self.generate_altered_db_table()
self.generate_altered_order_with_respect_to()
@@ -521,6 +529,9 @@ class MigrationAutodetector(object):
related_fields[field.name] = field
if getattr(field.remote_field, "through", None) and not field.remote_field.through._meta.auto_created:
related_fields[field.name] = field
+ # Are there any indexes to defer?
+ indexes = model_state.options['indexes']
+ model_state.options['indexes'] = []
# Are there unique/index_together to defer?
unique_together = model_state.options.pop('unique_together', None)
index_together = model_state.options.pop('index_together', None)
@@ -581,6 +592,15 @@ class MigrationAutodetector(object):
for name, field in sorted(related_fields.items())
]
related_dependencies.append((app_label, model_name, None, True))
+ for index in indexes:
+ self.add_operation(
+ app_label,
+ operations.AddIndex(
+ model_name=model_name,
+ index=index,
+ ),
+ dependencies=related_dependencies,
+ )
if unique_together:
self.add_operation(
app_label,
@@ -919,6 +939,46 @@ class MigrationAutodetector(object):
self._generate_removed_field(app_label, model_name, field_name)
self._generate_added_field(app_label, model_name, field_name)
+ def create_altered_indexes(self):
+ option_name = operations.AddIndex.option_name
+ for app_label, model_name in sorted(self.kept_model_keys):
+ old_model_name = self.renamed_models.get((app_label, model_name), model_name)
+ old_model_state = self.from_state.models[app_label, old_model_name]
+ new_model_state = self.to_state.models[app_label, model_name]
+
+ old_indexes = old_model_state.options[option_name]
+ new_indexes = new_model_state.options[option_name]
+ add_idx = [idx for idx in new_indexes if idx not in old_indexes]
+ rem_idx = [idx for idx in old_indexes if idx not in new_indexes]
+
+ self.altered_indexes.update({
+ (app_label, model_name): {
+ 'added_indexes': add_idx, 'removed_indexes': rem_idx,
+ }
+ })
+
+ def generate_added_indexes(self):
+ for (app_label, model_name), alt_indexes in self.altered_indexes.items():
+ for index in alt_indexes['added_indexes']:
+ self.add_operation(
+ app_label,
+ operations.AddIndex(
+ model_name=model_name,
+ index=index,
+ )
+ )
+
+ def generate_removed_indexes(self):
+ for (app_label, model_name), alt_indexes in self.altered_indexes.items():
+ for index in alt_indexes['removed_indexes']:
+ self.add_operation(
+ app_label,
+ operations.RemoveIndex(
+ model_name=model_name,
+ name=index.name,
+ )
+ )
+
def _get_dependencies_for_foreign_key(self, field):
# Account for FKs to swappable models
swappable_setting = getattr(field, 'swappable_setting', None)
diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py
index 12ec9ffdb8..32cfc96afb 100644
--- a/django/db/migrations/state.py
+++ b/django/db/migrations/state.py
@@ -353,6 +353,13 @@ class ModelState(object):
'ModelState.fields cannot refer to a model class - "%s.through" does. '
'Use a string reference instead.' % name
)
+ # Sanity-check that indexes have their name set.
+ for index in self.options['indexes']:
+ if not index.name:
+ raise ValueError(
+ "Indexes passed to ModelState require a name attribute. "
+ "%r doesn't have one." % index
+ )
@cached_property
def name_lower(self):
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 80d98b1399..e15ca00609 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -298,6 +298,13 @@ 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 abstract:
# Abstract base models can't be instantiated and don't appear in
# the list of models for an app. We do the final setup for them a
diff --git a/django/db/models/indexes.py b/django/db/models/indexes.py
index 694d904a87..e3f94f0bba 100644
--- a/django/db/models/indexes.py
+++ b/django/db/models/indexes.py
@@ -60,7 +60,7 @@ class Index(object):
def deconstruct(self):
path = '%s.%s' % (self.__class__.__module__, self.__class__.__name__)
path = path.replace('django.db.models.indexes', 'django.db.models')
- return (path, (), {'fields': self.fields})
+ return (path, (), {'fields': self.fields, 'name': self.name})
@staticmethod
def _hash_generator(*args):
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 19dce58663..670e2a5a2f 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -99,6 +99,7 @@ class Options(object):
self.db_table = ''
self.ordering = []
self._ordering_clash = False
+ self.indexes = []
self.unique_together = []
self.index_together = []
self.select_on_save = False