From 2abf417c815c20f41c0868d6f66520b32347106e Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 12 Sep 2023 05:56:16 +0200 Subject: [PATCH] Refs #27236 -- Removed Meta.index_together per deprecation timeline. --- django/db/backends/base/schema.py | 12 +- django/db/backends/sqlite3/schema.py | 10 - django/db/migrations/autodetector.py | 37 +- django/db/migrations/operations/models.py | 27 - django/db/migrations/state.py | 13 +- django/db/models/base.py | 31 -- django/db/models/options.py | 11 - docs/ref/checks.txt | 18 +- docs/ref/migration-operations.txt | 2 +- docs/ref/models/options.txt | 23 - docs/ref/schema-editor.txt | 5 +- docs/releases/1.11.txt | 4 +- docs/releases/1.5.txt | 3 +- docs/releases/1.7.txt | 4 +- docs/releases/4.1.txt | 4 +- docs/releases/4.2.txt | 5 +- docs/releases/5.1.txt | 2 + tests/indexes/tests.py | 29 +- tests/invalid_models_tests/test_models.py | 131 +---- tests/migrations/test_autodetector.py | 589 +--------------------- tests/migrations/test_base.py | 3 - tests/migrations/test_operations.py | 172 ------- tests/migrations/test_optimizer.py | 75 --- tests/migrations/test_state.py | 8 +- tests/schema/tests.py | 131 +---- 25 files changed, 41 insertions(+), 1308 deletions(-) diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index 493a47548a..191809441a 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -501,8 +501,7 @@ class BaseDatabaseSchemaEditor: model, field, field_type, field.db_comment ) ) - # Add any field index and index_together's (deferred as SQLite - # _remake_table needs it). + # Add any field index (deferred as SQLite _remake_table needs it). self.deferred_sql.extend(self._model_indexes_sql(model)) # Make M2M tables @@ -1585,8 +1584,8 @@ class BaseDatabaseSchemaEditor: def _model_indexes_sql(self, model): """ - Return a list of all index SQL statements (field indexes, - index_together, Meta.indexes) for the specified model. + Return a list of all index SQL statements (field indexes, Meta.indexes) + for the specified model. """ if not model._meta.managed or model._meta.proxy or model._meta.swapped: return [] @@ -1594,11 +1593,6 @@ class BaseDatabaseSchemaEditor: for field in model._meta.local_fields: output.extend(self._field_indexes_sql(model, field)) - # RemovedInDjango51Warning. - 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=fields, suffix="_idx")) - for index in model._meta.indexes: if ( not index.contains_expressions diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py index f311e0b745..a8200a1a8c 100644 --- a/django/db/backends/sqlite3/schema.py +++ b/django/db/backends/sqlite3/schema.py @@ -180,14 +180,6 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): for unique in model._meta.unique_together ] - # RemovedInDjango51Warning. - # Work out the new value for index_together, taking renames into - # account - index_together = [ - [rename_mapping.get(n, n) for n in index] - for index in model._meta.index_together - ] - indexes = model._meta.indexes if delete_field: indexes = [ @@ -210,7 +202,6 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): "app_label": model._meta.app_label, "db_table": model._meta.db_table, "unique_together": unique_together, - "index_together": index_together, # RemovedInDjango51Warning. "indexes": indexes, "constraints": constraints, "apps": apps, @@ -226,7 +217,6 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): "app_label": model._meta.app_label, "db_table": "new__%s" % strip_quotes(model._meta.db_table), "unique_together": unique_together, - "index_together": index_together, # RemovedInDjango51Warning. "indexes": indexes, "constraints": constraints, "apps": apps, diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py index 3a0ee511ff..a823b03738 100644 --- a/django/db/migrations/autodetector.py +++ b/django/db/migrations/autodetector.py @@ -190,14 +190,12 @@ class MigrationAutodetector: self.generate_renamed_indexes() # Generate removal of foo together. self.generate_removed_altered_unique_together() - self.generate_removed_altered_index_together() # RemovedInDjango51Warning. # Generate field operations. self.generate_removed_fields() self.generate_added_fields() self.generate_altered_fields() self.generate_altered_order_with_respect_to() self.generate_altered_unique_together() - self.generate_altered_index_together() # RemovedInDjango51Warning. self.generate_added_indexes() self.generate_added_constraints() self.generate_altered_db_table() @@ -584,7 +582,7 @@ class MigrationAutodetector: possible). Defer any model options that refer to collections of fields that might - be deferred (e.g. unique_together, index_together). + be deferred (e.g. unique_together). """ old_keys = self.old_model_keys | self.old_unmanaged_keys added_models = self.new_model_keys - old_keys @@ -608,12 +606,10 @@ class MigrationAutodetector: if getattr(field.remote_field, "through", None): related_fields[field_name] = field - # Are there indexes/unique|index_together to defer? + # Are there indexes/unique_together to defer? indexes = model_state.options.pop("indexes") constraints = model_state.options.pop("constraints") unique_together = model_state.options.pop("unique_together", None) - # RemovedInDjango51Warning. - index_together = model_state.options.pop("index_together", None) order_with_respect_to = model_state.options.pop( "order_with_respect_to", None ) @@ -742,16 +738,6 @@ class MigrationAutodetector: ), dependencies=related_dependencies, ) - # RemovedInDjango51Warning. - if index_together: - self.add_operation( - app_label, - operations.AlterIndexTogether( - name=model_name, - index_together=index_together, - ), - dependencies=related_dependencies, - ) # Fix relationships if the model changed from a proxy model to a # concrete model. relations = self.to_state.relations @@ -832,8 +818,6 @@ class MigrationAutodetector: related_fields[field_name] = field # Generate option removal first unique_together = model_state.options.pop("unique_together", None) - # RemovedInDjango51Warning. - index_together = model_state.options.pop("index_together", None) if unique_together: self.add_operation( app_label, @@ -842,15 +826,6 @@ class MigrationAutodetector: unique_together=None, ), ) - # RemovedInDjango51Warning. - if index_together: - self.add_operation( - app_label, - operations.AlterIndexTogether( - name=model_name, - index_together=None, - ), - ) # Then remove each related field for name in sorted(related_fields): self.add_operation( @@ -1525,10 +1500,6 @@ class MigrationAutodetector: def generate_removed_altered_unique_together(self): self._generate_removed_altered_foo_together(operations.AlterUniqueTogether) - # RemovedInDjango51Warning. - def generate_removed_altered_index_together(self): - self._generate_removed_altered_foo_together(operations.AlterIndexTogether) - def _generate_altered_foo_together(self, operation): for ( old_value, @@ -1548,10 +1519,6 @@ class MigrationAutodetector: def generate_altered_unique_together(self): self._generate_altered_foo_together(operations.AlterUniqueTogether) - # RemovedInDjango51Warning. - def generate_altered_index_together(self): - self._generate_altered_foo_together(operations.AlterIndexTogether) - def generate_altered_db_table(self): models_to_check = self.kept_model_keys.union( self.kept_proxy_keys, self.kept_unmanaged_keys diff --git a/django/db/migrations/operations/models.py b/django/db/migrations/operations/models.py index b18ef55369..d616cafb45 100644 --- a/django/db/migrations/operations/models.py +++ b/django/db/migrations/operations/models.py @@ -341,33 +341,6 @@ class CreateModel(ModelOperation): managers=self.managers, ), ] - elif isinstance(operation, RenameIndex) and operation.old_fields: - options_index_together = { - fields - for fields in self.options.get("index_together", []) - if fields != operation.old_fields - } - if options_index_together: - self.options["index_together"] = options_index_together - else: - self.options.pop("index_together", None) - return [ - CreateModel( - self.name, - fields=self.fields, - options={ - **self.options, - "indexes": [ - *self.options.get("indexes", []), - models.Index( - fields=operation.old_fields, name=operation.new_name - ), - ], - }, - bases=self.bases, - managers=self.managers, - ), - ] return super().reduce(operation, app_label) diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py index ae55967383..4aa6e1f6cc 100644 --- a/django/db/migrations/state.py +++ b/django/db/migrations/state.py @@ -310,14 +310,13 @@ class ProjectState: for from_field_name in from_fields ] ) - # Fix index/unique_together to refer to the new field. + # Fix unique_together to refer to the new field. options = model_state.options - for option in ("index_together", "unique_together"): - if option in options: - options[option] = [ - [new_name if n == old_name else n for n in together] - for together in options[option] - ] + if "unique_together" in options: + options["unique_together"] = [ + [new_name if n == old_name else n for n in together] + for together in options["unique_together"] + ] # Fix to_fields to refer to the new field. delay = True references = get_references(self, model_key, (old_name, found)) diff --git a/django/db/models/base.py b/django/db/models/base.py index 80503d118a..64d54380da 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -1595,7 +1595,6 @@ class Model(AltersData, metaclass=ModelBase): if not clash_errors: errors.extend(cls._check_column_name_clashes()) errors += [ - *cls._check_index_together(), *cls._check_unique_together(), *cls._check_indexes(databases), *cls._check_ordering(), @@ -1928,36 +1927,6 @@ class Model(AltersData, metaclass=ModelBase): ) return errors - # RemovedInDjango51Warning. - @classmethod - def _check_index_together(cls): - """Check the value of "index_together" option.""" - if not isinstance(cls._meta.index_together, (tuple, list)): - return [ - checks.Error( - "'index_together' must be a list or tuple.", - obj=cls, - id="models.E008", - ) - ] - - elif any( - not isinstance(fields, (tuple, list)) for fields in cls._meta.index_together - ): - return [ - checks.Error( - "All 'index_together' elements must be lists or tuples.", - obj=cls, - id="models.E009", - ) - ] - - else: - errors = [] - for fields in cls._meta.index_together: - errors.extend(cls._check_local_fields(fields, "index_together")) - return errors - @classmethod def _check_unique_together(cls): """Check the value of "unique_together" option.""" diff --git a/django/db/models/options.py b/django/db/models/options.py index 76017fc8b5..9b3106f67e 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -1,7 +1,6 @@ import bisect import copy import inspect -import warnings from collections import defaultdict from django.apps import apps @@ -11,7 +10,6 @@ from django.db import connections from django.db.models import AutoField, Manager, OrderWrt, UniqueConstraint from django.db.models.query_utils import PathInfo from django.utils.datastructures import ImmutableList, OrderedSet -from django.utils.deprecation import RemovedInDjango51Warning from django.utils.functional import cached_property from django.utils.module_loading import import_string from django.utils.text import camel_case_to_spaces, format_lazy @@ -43,7 +41,6 @@ DEFAULT_NAMES = ( "proxy", "swappable", "auto_created", - "index_together", # RemovedInDjango51Warning. "apps", "default_permissions", "select_on_save", @@ -119,7 +116,6 @@ class Options: self.indexes = [] self.constraints = [] self.unique_together = [] - self.index_together = [] # RemovedInDjango51Warning. self.select_on_save = False self.default_permissions = ("add", "change", "delete", "view") self.permissions = [] @@ -205,13 +201,6 @@ class Options: self.original_attrs[attr_name] = getattr(self, attr_name) self.unique_together = normalize_together(self.unique_together) - self.index_together = normalize_together(self.index_together) - if self.index_together: - warnings.warn( - f"'index_together' is deprecated. Use 'Meta.indexes' in " - f"{self.label!r} instead.", - RemovedInDjango51Warning, - ) # App label/class name interpolation for names of constraints and # indexes. if not getattr(cls._meta, "abstract", False): diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index 72699ac136..e641c989e3 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -345,21 +345,23 @@ Models ```` from model ````. * **models.E007**: Field ```` has column name ```` that is used by another field. -* **models.E008**: ``index_together`` must be a list or tuple. +* **models.E008**: ``index_together`` must be a list or tuple. *This check + appeared before Django 5.1.* * **models.E009**: All ``index_together`` elements must be lists or tuples. + *This check appeared before Django 5.1.* * **models.E010**: ``unique_together`` must be a list or tuple. * **models.E011**: All ``unique_together`` elements must be lists or tuples. -* **models.E012**: ``constraints/indexes/index_together/unique_together`` - refers to the nonexistent field ````. -* **models.E013**: ``constraints/indexes/index_together/unique_together`` - refers to a ``ManyToManyField`` ````, but ``ManyToManyField``\s - are not supported for that option. +* **models.E012**: ``constraints/indexes/unique_together`` refers to the + nonexistent field ````. +* **models.E013**: ``constraints/indexes/unique_together`` refers to a + ``ManyToManyField`` ````, but ``ManyToManyField``\s are not + supported for that option. * **models.E014**: ``ordering`` must be a tuple or list (even if you want to order by only one field). * **models.E015**: ``ordering`` refers to the nonexistent field, related field, or lookup ````. -* **models.E016**: ``constraints/indexes/index_together/unique_together`` - refers to field ```` which is not local to model ````. +* **models.E016**: ``constraints/indexes/unique_together`` refers to field + ```` which is not local to model ````. * **models.E017**: Proxy model ```` contains model fields. * **models.E018**: Autogenerated column name too long for field ````. Maximum length is ```` for database ````. diff --git a/docs/ref/migration-operations.txt b/docs/ref/migration-operations.txt index 7cc3a3926f..e8d8630851 100644 --- a/docs/ref/migration-operations.txt +++ b/docs/ref/migration-operations.txt @@ -247,7 +247,7 @@ Removes the index named ``name`` from the model with ``model_name``. Renames an index in the database table for the model with ``model_name``. Exactly one of ``old_name`` and ``old_fields`` can be provided. ``old_fields`` is an iterable of the strings, often corresponding to fields of -:attr:`~django.db.models.Options.index_together`. +``index_together`` (pre-Django 5.1 option). On databases that don't support an index renaming statement (SQLite and MariaDB < 10.5.2), the operation will drop and recreate the index, which can be diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 2a383b2b88..909577be6c 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -451,29 +451,6 @@ not be looking at your Django code. For example:: The ``ValidationError`` raised during model validation when the constraint is violated has the ``unique_together`` error code. -``index_together`` ------------------- - -.. attribute:: Options.index_together - - Sets of field names that, taken together, are indexed:: - - index_together = [ - ["pub_date", "deadline"], - ] - - This list of fields will be indexed together (i.e. the appropriate - ``CREATE INDEX`` statement will be issued.) - - For convenience, ``index_together`` can be a single list when dealing with a single - set of fields:: - - index_together = ["pub_date", "deadline"] - - .. deprecated:: 4.2 - - Use the :attr:`~Options.indexes` option instead. - ``constraints`` --------------- diff --git a/docs/ref/schema-editor.txt b/docs/ref/schema-editor.txt index 635272c982..b459e646bc 100644 --- a/docs/ref/schema-editor.txt +++ b/docs/ref/schema-editor.txt @@ -114,9 +114,8 @@ the new value. .. method:: BaseDatabaseSchemaEditor.alter_index_together(model, old_index_together, new_index_together) -Changes a model's :attr:`~django.db.models.Options.index_together` value; this -will add or remove indexes from the model's table until they match the new -value. +Changes a model's ``index_together`` value; this will add or remove indexes +from the model's table until they match the new value. ``alter_db_table()`` -------------------- diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 74db5fcf0d..a178257cd0 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -59,8 +59,8 @@ creating database indexes. Indexes are added to models using the The :class:`~django.db.models.Index` class creates a b-tree index, as if you used :attr:`~django.db.models.Field.db_index` on the model field or -:attr:`~django.db.models.Options.index_together` on the model ``Meta`` class. -It can be subclassed to support different index types, such as +``index_together`` on the model ``Meta`` class. It can be subclassed to support +different index types, such as :class:`~django.contrib.postgres.indexes.GinIndex`. It also allows defining the order (ASC/DESC) for the columns of the index. diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 09284e6b35..76d41a9ab8 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -329,8 +329,7 @@ Django 1.5 also includes several smaller improvements worth noting: session data in a non-default cache. * Multi-column indexes can now be created on models. Read the - :attr:`~django.db.models.Options.index_together` documentation for more - information. + ``index_together`` documentation for more information. * During Django's logging configuration verbose Deprecation warnings are enabled and warnings are captured into the logging system. Logged warnings diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index e311e902d8..87246be6e4 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -778,8 +778,8 @@ Models an error (before that, it would either result in a database error or incorrect data). -* You can use a single list for :attr:`~django.db.models.Options.index_together` - (rather than a list of lists) when specifying a single set of fields. +* You can use a single list for ``index_together`` (rather than a list of + lists) when specifying a single set of fields. * Custom intermediate models having more than one foreign key to any of the models participating in a many-to-many relationship are now permitted, diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt index d93833b7f7..c840db4a7f 100644 --- a/docs/releases/4.1.txt +++ b/docs/releases/4.1.txt @@ -317,7 +317,7 @@ Migrations * The new :class:`~django.db.migrations.operations.RenameIndex` operation allows renaming indexes defined in the :attr:`Meta.indexes ` or - :attr:`~django.db.models.Options.index_together` options. + ``index_together`` options. * The migrations autodetector now generates :class:`~django.db.migrations.operations.RenameIndex` operations instead of @@ -327,7 +327,7 @@ Migrations * The migrations autodetector now generates :class:`~django.db.migrations.operations.RenameIndex` operations instead of ``AlterIndexTogether`` and ``AddIndex``, when moving indexes defined in the - :attr:`Meta.index_together ` to the + ``Meta.index_together`` to the :attr:`Meta.indexes `. Models diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt index 7d7848708d..0a52f3c813 100644 --- a/docs/releases/4.2.txt +++ b/docs/releases/4.2.txt @@ -477,9 +477,8 @@ Features deprecated in 4.2 ``index_together`` option is deprecated in favor of ``indexes`` --------------------------------------------------------------- -The :attr:`Meta.index_together ` -option is deprecated in favor of the :attr:`~django.db.models.Options.indexes` -option. +The ``Meta.index_together`` option is deprecated in favor of the +:attr:`~django.db.models.Options.indexes` option. Migrating existing ``index_together`` should be handled as a migration. For example:: diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt index 4560e1fabf..c8d6f5f3f6 100644 --- a/docs/releases/5.1.txt +++ b/docs/releases/5.1.txt @@ -252,3 +252,5 @@ See :ref:`deprecated-features-4.2` for details on these changes, including how to remove usage of these features. * The ``BaseUserManager.make_random_password()`` method is removed. + +* The model's ``Meta.index_together`` option is removed. diff --git a/tests/indexes/tests.py b/tests/indexes/tests.py index 8fdaec25f5..107703c39a 100644 --- a/tests/indexes/tests.py +++ b/tests/indexes/tests.py @@ -3,26 +3,16 @@ from unittest import skipUnless from django.conf import settings from django.db import connection -from django.db.models import ( - CASCADE, - CharField, - DateTimeField, - ForeignKey, - Index, - Model, - Q, -) +from django.db.models import CASCADE, ForeignKey, Index, Q from django.db.models.functions import Lower from django.test import ( TestCase, TransactionTestCase, - ignore_warnings, skipIfDBFeature, skipUnlessDBFeature, ) -from django.test.utils import isolate_apps, override_settings +from django.test.utils import override_settings from django.utils import timezone -from django.utils.deprecation import RemovedInDjango51Warning from .models import Article, ArticleTranslation, IndexedArticle2 @@ -80,21 +70,6 @@ class SchemaIndexesTests(TestCase): index_sql[0], ) - @ignore_warnings(category=RemovedInDjango51Warning) - @isolate_apps("indexes") - def test_index_together_single_list(self): - class IndexTogetherSingleList(Model): - headline = CharField(max_length=100) - pub_date = DateTimeField() - - class Meta: - index_together = ["headline", "pub_date"] - - index_sql = connection.schema_editor()._model_indexes_sql( - IndexTogetherSingleList - ) - self.assertEqual(len(index_sql), 1) - def test_columns_list_sql(self): index = Index(fields=["headline"], name="whitespace_idx") editor = connection.schema_editor() diff --git a/tests/invalid_models_tests/test_models.py b/tests/invalid_models_tests/test_models.py index dc52f58c44..f22f273c9a 100644 --- a/tests/invalid_models_tests/test_models.py +++ b/tests/invalid_models_tests/test_models.py @@ -5,9 +5,8 @@ from django.core.checks.model_checks import _check_lazy_references from django.db import connection, connections, models from django.db.models.functions import Abs, Lower, Round from django.db.models.signals import post_init -from django.test import SimpleTestCase, TestCase, ignore_warnings, skipUnlessDBFeature +from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature from django.test.utils import isolate_apps, override_settings, register_lookup -from django.utils.deprecation import RemovedInDjango51Warning class EmptyRouter: @@ -29,134 +28,6 @@ def get_max_column_name_length(): return (allowed_len, db_alias) -@isolate_apps("invalid_models_tests") -@ignore_warnings(category=RemovedInDjango51Warning) -class IndexTogetherTests(SimpleTestCase): - def test_non_iterable(self): - class Model(models.Model): - class Meta: - index_together = 42 - - self.assertEqual( - Model.check(), - [ - Error( - "'index_together' must be a list or tuple.", - obj=Model, - id="models.E008", - ), - ], - ) - - def test_non_list(self): - class Model(models.Model): - class Meta: - index_together = "not-a-list" - - self.assertEqual( - Model.check(), - [ - Error( - "'index_together' must be a list or tuple.", - obj=Model, - id="models.E008", - ), - ], - ) - - def test_list_containing_non_iterable(self): - class Model(models.Model): - class Meta: - index_together = [("a", "b"), 42] - - self.assertEqual( - Model.check(), - [ - Error( - "All 'index_together' elements must be lists or tuples.", - obj=Model, - id="models.E009", - ), - ], - ) - - def test_pointing_to_missing_field(self): - class Model(models.Model): - class Meta: - index_together = [["missing_field"]] - - self.assertEqual( - Model.check(), - [ - Error( - "'index_together' refers to the nonexistent field 'missing_field'.", - obj=Model, - id="models.E012", - ), - ], - ) - - def test_pointing_to_non_local_field(self): - class Foo(models.Model): - field1 = models.IntegerField() - - class Bar(Foo): - field2 = models.IntegerField() - - class Meta: - index_together = [["field2", "field1"]] - - self.assertEqual( - Bar.check(), - [ - Error( - "'index_together' refers to field 'field1' which is not " - "local to model 'Bar'.", - hint="This issue may be caused by multi-table inheritance.", - obj=Bar, - id="models.E016", - ), - ], - ) - - def test_pointing_to_m2m_field(self): - class Model(models.Model): - m2m = models.ManyToManyField("self") - - class Meta: - index_together = [["m2m"]] - - self.assertEqual( - Model.check(), - [ - Error( - "'index_together' refers to a ManyToManyField 'm2m', but " - "ManyToManyFields are not permitted in 'index_together'.", - obj=Model, - id="models.E013", - ), - ], - ) - - def test_pointing_to_fk(self): - class Foo(models.Model): - pass - - class Bar(models.Model): - foo_1 = models.ForeignKey( - Foo, on_delete=models.CASCADE, related_name="bar_1" - ) - foo_2 = models.ForeignKey( - Foo, on_delete=models.CASCADE, related_name="bar_2" - ) - - class Meta: - index_together = [["foo_1_id", "foo_2"]] - - self.assertEqual(Bar.check(), []) - - -# unique_together tests are very similar to index_together tests. @isolate_apps("invalid_models_tests") class UniqueTogetherTests(SimpleTestCase): def test_non_iterable(self): diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py index 85674e552a..c54349313e 100644 --- a/tests/migrations/test_autodetector.py +++ b/tests/migrations/test_autodetector.py @@ -13,9 +13,8 @@ from django.db.migrations.graph import MigrationGraph from django.db.migrations.loader import MigrationLoader from django.db.migrations.questioner import MigrationQuestioner from django.db.migrations.state import ModelState, ProjectState -from django.test import SimpleTestCase, TestCase, ignore_warnings, override_settings +from django.test import SimpleTestCase, TestCase, override_settings from django.test.utils import isolate_lru_cache -from django.utils.deprecation import RemovedInDjango51Warning from .models import FoodManager, FoodQuerySet @@ -4872,592 +4871,6 @@ class AutodetectorTests(BaseAutodetectorTests): self.assertOperationAttributes(changes, "testapp", 0, 0, name="Book") -@ignore_warnings(category=RemovedInDjango51Warning) -class AutodetectorIndexTogetherTests(BaseAutodetectorTests): - book_index_together = ModelState( - "otherapp", - "Book", - [ - ("id", models.AutoField(primary_key=True)), - ("author", models.ForeignKey("testapp.Author", models.CASCADE)), - ("title", models.CharField(max_length=200)), - ], - { - "index_together": {("author", "title")}, - }, - ) - book_index_together_2 = ModelState( - "otherapp", - "Book", - [ - ("id", models.AutoField(primary_key=True)), - ("author", models.ForeignKey("testapp.Author", models.CASCADE)), - ("title", models.CharField(max_length=200)), - ], - { - "index_together": {("title", "author")}, - }, - ) - book_index_together_3 = ModelState( - "otherapp", - "Book", - [ - ("id", models.AutoField(primary_key=True)), - ("newfield", models.IntegerField()), - ("author", models.ForeignKey("testapp.Author", models.CASCADE)), - ("title", models.CharField(max_length=200)), - ], - { - "index_together": {("title", "newfield")}, - }, - ) - book_index_together_4 = ModelState( - "otherapp", - "Book", - [ - ("id", models.AutoField(primary_key=True)), - ("newfield2", models.IntegerField()), - ("author", models.ForeignKey("testapp.Author", models.CASCADE)), - ("title", models.CharField(max_length=200)), - ], - { - "index_together": {("title", "newfield2")}, - }, - ) - - def test_empty_index_together(self): - """Empty index_together shouldn't generate a migration.""" - # Explicitly testing for not specified, since this is the case after - # a CreateModel operation w/o any definition on the original model - model_state_not_specified = ModelState( - "a", "model", [("id", models.AutoField(primary_key=True))] - ) - # Explicitly testing for None, since this was the issue in #23452 after - # an AlterIndexTogether operation with e.g. () as value - model_state_none = ModelState( - "a", - "model", - [("id", models.AutoField(primary_key=True))], - { - "index_together": None, - }, - ) - # Explicitly testing for the empty set, since we now always have sets. - # During removal (('col1', 'col2'),) --> () this becomes set([]) - model_state_empty = ModelState( - "a", - "model", - [("id", models.AutoField(primary_key=True))], - { - "index_together": set(), - }, - ) - - def test(from_state, to_state, msg): - changes = self.get_changes([from_state], [to_state]) - if changes: - ops = ", ".join( - o.__class__.__name__ for o in changes["a"][0].operations - ) - self.fail("Created operation(s) %s from %s" % (ops, msg)) - - tests = ( - ( - model_state_not_specified, - model_state_not_specified, - '"not specified" to "not specified"', - ), - (model_state_not_specified, model_state_none, '"not specified" to "None"'), - ( - model_state_not_specified, - model_state_empty, - '"not specified" to "empty"', - ), - (model_state_none, model_state_not_specified, '"None" to "not specified"'), - (model_state_none, model_state_none, '"None" to "None"'), - (model_state_none, model_state_empty, '"None" to "empty"'), - ( - model_state_empty, - model_state_not_specified, - '"empty" to "not specified"', - ), - (model_state_empty, model_state_none, '"empty" to "None"'), - (model_state_empty, model_state_empty, '"empty" to "empty"'), - ) - - for t in tests: - test(*t) - - def test_rename_index_together_to_index(self): - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together], - [AutodetectorTests.author_empty, AutodetectorTests.book_indexes], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes(changes, "otherapp", 0, ["RenameIndex"]) - self.assertOperationAttributes( - changes, - "otherapp", - 0, - 0, - model_name="book", - new_name="book_title_author_idx", - old_fields=("author", "title"), - ) - - def test_rename_index_together_to_index_extra_options(self): - # Indexes with extra options don't match indexes in index_together. - book_partial_index = ModelState( - "otherapp", - "Book", - [ - ("id", models.AutoField(primary_key=True)), - ("author", models.ForeignKey("testapp.Author", models.CASCADE)), - ("title", models.CharField(max_length=200)), - ], - { - "indexes": [ - models.Index( - fields=["author", "title"], - condition=models.Q(title__startswith="The"), - name="book_title_author_idx", - ) - ], - }, - ) - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together], - [AutodetectorTests.author_empty, book_partial_index], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["AlterIndexTogether", "AddIndex"], - ) - - def test_rename_index_together_to_index_order_fields(self): - # Indexes with reordered fields don't match indexes in index_together. - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together], - [AutodetectorTests.author_empty, AutodetectorTests.book_unordered_indexes], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["AlterIndexTogether", "AddIndex"], - ) - - def test_add_index_together(self): - changes = self.get_changes( - [AutodetectorTests.author_empty, AutodetectorTests.book], - [AutodetectorTests.author_empty, self.book_index_together], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes(changes, "otherapp", 0, ["AlterIndexTogether"]) - self.assertOperationAttributes( - changes, "otherapp", 0, 0, name="book", index_together={("author", "title")} - ) - - def test_remove_index_together(self): - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together], - [AutodetectorTests.author_empty, AutodetectorTests.book], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes(changes, "otherapp", 0, ["AlterIndexTogether"]) - self.assertOperationAttributes( - changes, "otherapp", 0, 0, name="book", index_together=set() - ) - - def test_index_together_remove_fk(self): - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together], - [AutodetectorTests.author_empty, AutodetectorTests.book_with_no_author], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["AlterIndexTogether", "RemoveField"], - ) - self.assertOperationAttributes( - changes, "otherapp", 0, 0, name="book", index_together=set() - ) - self.assertOperationAttributes( - changes, "otherapp", 0, 1, model_name="book", name="author" - ) - - def test_index_together_no_changes(self): - """ - index_together doesn't generate a migration if no changes have been - made. - """ - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together], - [AutodetectorTests.author_empty, self.book_index_together], - ) - self.assertEqual(len(changes), 0) - - def test_index_together_ordering(self): - """index_together triggers on ordering changes.""" - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together], - [AutodetectorTests.author_empty, self.book_index_together_2], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["AlterIndexTogether"], - ) - self.assertOperationAttributes( - changes, - "otherapp", - 0, - 0, - name="book", - index_together={("title", "author")}, - ) - - def test_add_field_and_index_together(self): - """ - Added fields will be created before using them in index_together. - """ - changes = self.get_changes( - [AutodetectorTests.author_empty, AutodetectorTests.book], - [AutodetectorTests.author_empty, self.book_index_together_3], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["AddField", "AlterIndexTogether"], - ) - self.assertOperationAttributes( - changes, - "otherapp", - 0, - 1, - name="book", - index_together={("title", "newfield")}, - ) - - def test_create_model_and_index_together(self): - author = ModelState( - "otherapp", - "Author", - [ - ("id", models.AutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ], - ) - book_with_author = ModelState( - "otherapp", - "Book", - [ - ("id", models.AutoField(primary_key=True)), - ("author", models.ForeignKey("otherapp.Author", models.CASCADE)), - ("title", models.CharField(max_length=200)), - ], - { - "index_together": {("title", "author")}, - }, - ) - changes = self.get_changes( - [AutodetectorTests.book_with_no_author], [author, book_with_author] - ) - self.assertEqual(len(changes["otherapp"]), 1) - migration = changes["otherapp"][0] - self.assertEqual(len(migration.operations), 3) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["CreateModel", "AddField", "AlterIndexTogether"], - ) - - def test_remove_field_and_index_together(self): - """ - Removed fields will be removed after updating index_together. - """ - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together_3], - [AutodetectorTests.author_empty, self.book_index_together], - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["AlterIndexTogether", "RemoveField"], - ) - self.assertOperationAttributes( - changes, - "otherapp", - 0, - 0, - name="book", - index_together={("author", "title")}, - ) - self.assertOperationAttributes( - changes, - "otherapp", - 0, - 1, - model_name="book", - name="newfield", - ) - - def test_alter_field_and_index_together(self): - """Fields are altered after deleting some index_together.""" - initial_author = ModelState( - "testapp", - "Author", - [ - ("id", models.AutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ("age", models.IntegerField(db_index=True)), - ], - { - "index_together": {("name",)}, - }, - ) - author_reversed_constraints = ModelState( - "testapp", - "Author", - [ - ("id", models.AutoField(primary_key=True)), - ("name", models.CharField(max_length=200, unique=True)), - ("age", models.IntegerField()), - ], - { - "index_together": {("age",)}, - }, - ) - changes = self.get_changes([initial_author], [author_reversed_constraints]) - - self.assertNumberMigrations(changes, "testapp", 1) - self.assertOperationTypes( - changes, - "testapp", - 0, - [ - "AlterIndexTogether", - "AlterField", - "AlterField", - "AlterIndexTogether", - ], - ) - self.assertOperationAttributes( - changes, - "testapp", - 0, - 0, - name="author", - index_together=set(), - ) - self.assertOperationAttributes( - changes, - "testapp", - 0, - 1, - model_name="author", - name="age", - ) - self.assertOperationAttributes( - changes, - "testapp", - 0, - 2, - model_name="author", - name="name", - ) - self.assertOperationAttributes( - changes, - "testapp", - 0, - 3, - name="author", - index_together={("age",)}, - ) - - def test_partly_alter_index_together_increase(self): - initial_author = ModelState( - "testapp", - "Author", - [ - ("id", models.AutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ("age", models.IntegerField()), - ], - { - "index_together": {("name",)}, - }, - ) - author_new_constraints = ModelState( - "testapp", - "Author", - [ - ("id", models.AutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ("age", models.IntegerField()), - ], - { - "index_together": {("name",), ("age",)}, - }, - ) - changes = self.get_changes([initial_author], [author_new_constraints]) - - self.assertNumberMigrations(changes, "testapp", 1) - self.assertOperationTypes( - changes, - "testapp", - 0, - ["AlterIndexTogether"], - ) - self.assertOperationAttributes( - changes, - "testapp", - 0, - 0, - name="author", - index_together={("name",), ("age",)}, - ) - - def test_partly_alter_index_together_decrease(self): - initial_author = ModelState( - "testapp", - "Author", - [ - ("id", models.AutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ("age", models.IntegerField()), - ], - { - "index_together": {("name",), ("age",)}, - }, - ) - author_new_constraints = ModelState( - "testapp", - "Author", - [ - ("id", models.AutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ("age", models.IntegerField()), - ], - { - "index_together": {("age",)}, - }, - ) - changes = self.get_changes([initial_author], [author_new_constraints]) - - self.assertNumberMigrations(changes, "testapp", 1) - self.assertOperationTypes( - changes, - "testapp", - 0, - ["AlterIndexTogether"], - ) - self.assertOperationAttributes( - changes, - "testapp", - 0, - 0, - name="author", - index_together={("age",)}, - ) - - def test_rename_field_and_index_together(self): - """Fields are renamed before updating index_together.""" - changes = self.get_changes( - [AutodetectorTests.author_empty, self.book_index_together_3], - [AutodetectorTests.author_empty, self.book_index_together_4], - MigrationQuestioner({"ask_rename": True}), - ) - self.assertNumberMigrations(changes, "otherapp", 1) - self.assertOperationTypes( - changes, - "otherapp", - 0, - ["RenameField", "AlterIndexTogether"], - ) - self.assertOperationAttributes( - changes, - "otherapp", - 0, - 1, - name="book", - index_together={("title", "newfield2")}, - ) - - def test_add_model_order_with_respect_to_index_together(self): - changes = self.get_changes( - [], - [ - AutodetectorTests.book, - ModelState( - "testapp", - "Author", - [ - ("id", models.AutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ("book", models.ForeignKey("otherapp.Book", models.CASCADE)), - ], - options={ - "order_with_respect_to": "book", - "index_together": {("name", "_order")}, - }, - ), - ], - ) - self.assertNumberMigrations(changes, "testapp", 1) - self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"]) - self.assertOperationAttributes( - changes, - "testapp", - 0, - 0, - name="Author", - options={ - "order_with_respect_to": "book", - "index_together": {("name", "_order")}, - }, - ) - - def test_set_alter_order_with_respect_to_index_together(self): - after = ModelState( - "testapp", - "Author", - [ - ("id", models.AutoField(primary_key=True)), - ("name", models.CharField(max_length=200)), - ("book", models.ForeignKey("otherapp.Book", models.CASCADE)), - ], - options={ - "order_with_respect_to": "book", - "index_together": {("name", "_order")}, - }, - ) - changes = self.get_changes( - [AutodetectorTests.book, AutodetectorTests.author_with_book], - [AutodetectorTests.book, after], - ) - self.assertNumberMigrations(changes, "testapp", 1) - self.assertOperationTypes( - changes, - "testapp", - 0, - ["AlterOrderWithRespectTo", "AlterIndexTogether"], - ) - - class MigrationSuggestNameTests(SimpleTestCase): def test_no_operations(self): class Migration(migrations.Migration): diff --git a/tests/migrations/test_base.py b/tests/migrations/test_base.py index b5228ad445..0ff1dda1d9 100644 --- a/tests/migrations/test_base.py +++ b/tests/migrations/test_base.py @@ -269,7 +269,6 @@ class OperationTestBase(MigrationTestBase): unique_together=False, options=False, db_table=None, - index_together=False, # RemovedInDjango51Warning. constraints=None, indexes=None, ): @@ -277,8 +276,6 @@ class OperationTestBase(MigrationTestBase): # Make the "current" state. model_options = { "swappable": "TEST_SWAP_MODEL", - # RemovedInDjango51Warning. - "index_together": [["weight", "pink"]] if index_together else [], "unique_together": [["pink", "weight"]] if unique_together else [], } if options: diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index 01197d5fc8..c77197cc56 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -11,13 +11,11 @@ from django.db.models.functions import Abs, Pi from django.db.transaction import atomic from django.test import ( SimpleTestCase, - ignore_warnings, override_settings, skipIfDBFeature, skipUnlessDBFeature, ) from django.test.utils import CaptureQueriesContext -from django.utils.deprecation import RemovedInDjango51Warning from .models import FoodManager, FoodQuerySet, UnicodeModel from .test_base import OperationTestBase @@ -3094,37 +3092,6 @@ class OperationTests(OperationTestBase): self.assertColumnExists("test_rnflut_pony", "pink") self.assertColumnNotExists("test_rnflut_pony", "blue") - @ignore_warnings(category=RemovedInDjango51Warning) - def test_rename_field_index_together(self): - project_state = self.set_up_test_model("test_rnflit", index_together=True) - operation = migrations.RenameField("Pony", "pink", "blue") - new_state = project_state.clone() - operation.state_forwards("test_rnflit", new_state) - self.assertIn("blue", new_state.models["test_rnflit", "pony"].fields) - self.assertNotIn("pink", new_state.models["test_rnflit", "pony"].fields) - # index_together has the renamed column. - self.assertIn( - "blue", new_state.models["test_rnflit", "pony"].options["index_together"][0] - ) - self.assertNotIn( - "pink", new_state.models["test_rnflit", "pony"].options["index_together"][0] - ) - # Rename field. - self.assertColumnExists("test_rnflit_pony", "pink") - self.assertColumnNotExists("test_rnflit_pony", "blue") - with connection.schema_editor() as editor: - operation.database_forwards("test_rnflit", editor, project_state, new_state) - self.assertColumnExists("test_rnflit_pony", "blue") - self.assertColumnNotExists("test_rnflit_pony", "pink") - # The index constraint has been ported over. - self.assertIndexExists("test_rnflit_pony", ["weight", "blue"]) - # Reversal. - with connection.schema_editor() as editor: - operation.database_backwards( - "test_rnflit", editor, new_state, project_state - ) - self.assertIndexExists("test_rnflit_pony", ["weight", "pink"]) - def test_rename_field_with_db_column(self): project_state = self.apply_operations( "test_rfwdbc", @@ -3601,52 +3568,6 @@ class OperationTests(OperationTestBase): with self.assertRaisesMessage(ValueError, msg): migrations.RenameIndex("Pony", new_name="new_idx_name") - @ignore_warnings(category=RemovedInDjango51Warning) - def test_rename_index_unnamed_index(self): - app_label = "test_rninui" - project_state = self.set_up_test_model(app_label, index_together=True) - table_name = app_label + "_pony" - self.assertIndexNameNotExists(table_name, "new_pony_test_idx") - operation = migrations.RenameIndex( - "Pony", new_name="new_pony_test_idx", old_fields=("weight", "pink") - ) - self.assertEqual( - operation.describe(), - "Rename unnamed index for ('weight', 'pink') on Pony to new_pony_test_idx", - ) - self.assertEqual( - operation.migration_name_fragment, - "rename_pony_weight_pink_new_pony_test_idx", - ) - - new_state = project_state.clone() - operation.state_forwards(app_label, new_state) - # Rename index. - with connection.schema_editor() as editor: - operation.database_forwards(app_label, editor, project_state, new_state) - self.assertIndexNameExists(table_name, "new_pony_test_idx") - # Reverse is a no-op. - with connection.schema_editor() as editor, self.assertNumQueries(0): - operation.database_backwards(app_label, editor, new_state, project_state) - self.assertIndexNameExists(table_name, "new_pony_test_idx") - # Reapply, RenameIndex operation is a noop when the old and new name - # match. - with connection.schema_editor() as editor: - operation.database_forwards(app_label, editor, new_state, project_state) - self.assertIndexNameExists(table_name, "new_pony_test_idx") - # Deconstruction. - definition = operation.deconstruct() - self.assertEqual(definition[0], "RenameIndex") - self.assertEqual(definition[1], []) - self.assertEqual( - definition[2], - { - "model_name": "Pony", - "new_name": "new_pony_test_idx", - "old_fields": ("weight", "pink"), - }, - ) - def test_rename_index_unknown_unnamed_index(self): app_label = "test_rninuui" project_state = self.set_up_test_model(app_label) @@ -3697,22 +3618,6 @@ class OperationTests(OperationTestBase): self.assertIsNot(old_model, new_model) self.assertEqual(new_model._meta.indexes[0].name, "new_pony_pink_idx") - @ignore_warnings(category=RemovedInDjango51Warning) - def test_rename_index_state_forwards_unnamed_index(self): - app_label = "test_rnidsfui" - project_state = self.set_up_test_model(app_label, index_together=True) - old_model = project_state.apps.get_model(app_label, "Pony") - new_state = project_state.clone() - - operation = migrations.RenameIndex( - "Pony", new_name="new_pony_pink_idx", old_fields=("weight", "pink") - ) - operation.state_forwards(app_label, new_state) - new_model = new_state.apps.get_model(app_label, "Pony") - self.assertIsNot(old_model, new_model) - self.assertEqual(new_model._meta.index_together, tuple()) - self.assertEqual(new_model._meta.indexes[0].name, "new_pony_pink_idx") - @skipUnlessDBFeature("supports_expression_indexes") def test_add_func_index(self): app_label = "test_addfuncin" @@ -3832,89 +3737,12 @@ class OperationTests(OperationTestBase): # Ensure the index is still there self.assertIndexExists("test_alflin_pony", ["pink"]) - @ignore_warnings(category=RemovedInDjango51Warning) - def test_alter_index_together(self): - """ - Tests the AlterIndexTogether operation. - """ - project_state = self.set_up_test_model("test_alinto") - # Test the state alteration - operation = migrations.AlterIndexTogether("Pony", [("pink", "weight")]) - self.assertEqual( - operation.describe(), "Alter index_together for Pony (1 constraint(s))" - ) - self.assertEqual( - operation.migration_name_fragment, - "alter_pony_index_together", - ) - new_state = project_state.clone() - operation.state_forwards("test_alinto", new_state) - self.assertEqual( - len( - project_state.models["test_alinto", "pony"].options.get( - "index_together", set() - ) - ), - 0, - ) - self.assertEqual( - len( - new_state.models["test_alinto", "pony"].options.get( - "index_together", set() - ) - ), - 1, - ) - # Make sure there's no matching index - self.assertIndexNotExists("test_alinto_pony", ["pink", "weight"]) - # Test the database alteration - with connection.schema_editor() as editor: - operation.database_forwards("test_alinto", editor, project_state, new_state) - self.assertIndexExists("test_alinto_pony", ["pink", "weight"]) - # And test reversal - with connection.schema_editor() as editor: - operation.database_backwards( - "test_alinto", editor, new_state, project_state - ) - self.assertIndexNotExists("test_alinto_pony", ["pink", "weight"]) - # And deconstruction - definition = operation.deconstruct() - self.assertEqual(definition[0], "AlterIndexTogether") - self.assertEqual(definition[1], []) - self.assertEqual( - definition[2], {"name": "Pony", "index_together": {("pink", "weight")}} - ) - def test_alter_index_together_remove(self): operation = migrations.AlterIndexTogether("Pony", None) self.assertEqual( operation.describe(), "Alter index_together for Pony (0 constraint(s))" ) - @skipUnlessDBFeature("allows_multiple_constraints_on_same_fields") - @ignore_warnings(category=RemovedInDjango51Warning) - def test_alter_index_together_remove_with_unique_together(self): - app_label = "test_alintoremove_wunto" - table_name = "%s_pony" % app_label - project_state = self.set_up_test_model(app_label, unique_together=True) - self.assertUniqueConstraintExists(table_name, ["pink", "weight"]) - # Add index together. - new_state = project_state.clone() - operation = migrations.AlterIndexTogether("Pony", [("pink", "weight")]) - operation.state_forwards(app_label, new_state) - with connection.schema_editor() as editor: - operation.database_forwards(app_label, editor, project_state, new_state) - self.assertIndexExists(table_name, ["pink", "weight"]) - # Remove index together. - project_state = new_state - new_state = project_state.clone() - operation = migrations.AlterIndexTogether("Pony", set()) - operation.state_forwards(app_label, new_state) - with connection.schema_editor() as editor: - operation.database_forwards(app_label, editor, project_state, new_state) - self.assertIndexNotExists(table_name, ["pink", "weight"]) - self.assertUniqueConstraintExists(table_name, ["pink", "weight"]) - def test_add_constraint(self): project_state = self.set_up_test_model("test_addconstraint") gt_check = models.Q(pink__gt=2) diff --git a/tests/migrations/test_optimizer.py b/tests/migrations/test_optimizer.py index ec5f6b4ce9..ece6580ad8 100644 --- a/tests/migrations/test_optimizer.py +++ b/tests/migrations/test_optimizer.py @@ -1293,81 +1293,6 @@ class OptimizerTests(SimpleTestCase): ], ) - def test_create_model_remove_index_together_rename_index(self): - self.assertOptimizesTo( - [ - migrations.CreateModel( - name="Pony", - fields=[ - ("weight", models.IntegerField()), - ("age", models.IntegerField()), - ], - options={ - "index_together": [("age", "weight")], - }, - ), - migrations.RenameIndex( - "Pony", new_name="idx_pony_age_weight", old_fields=("age", "weight") - ), - ], - [ - migrations.CreateModel( - name="Pony", - fields=[ - ("weight", models.IntegerField()), - ("age", models.IntegerField()), - ], - options={ - "indexes": [ - models.Index( - fields=["age", "weight"], name="idx_pony_age_weight" - ), - ], - }, - ), - ], - ) - - def test_create_model_index_together_rename_index(self): - self.assertOptimizesTo( - [ - migrations.CreateModel( - name="Pony", - fields=[ - ("weight", models.IntegerField()), - ("age", models.IntegerField()), - ("height", models.IntegerField()), - ("rank", models.IntegerField()), - ], - options={ - "index_together": [("age", "weight"), ("height", "rank")], - }, - ), - migrations.RenameIndex( - "Pony", new_name="idx_pony_age_weight", old_fields=("age", "weight") - ), - ], - [ - migrations.CreateModel( - name="Pony", - fields=[ - ("weight", models.IntegerField()), - ("age", models.IntegerField()), - ("height", models.IntegerField()), - ("rank", models.IntegerField()), - ], - options={ - "index_together": {("height", "rank")}, - "indexes": [ - models.Index( - fields=["age", "weight"], name="idx_pony_age_weight" - ), - ], - }, - ), - ], - ) - def test_create_model_rename_index_no_old_fields(self): self.assertOptimizesTo( [ diff --git a/tests/migrations/test_state.py b/tests/migrations/test_state.py index 55de9b3a67..f1ac78c992 100644 --- a/tests/migrations/test_state.py +++ b/tests/migrations/test_state.py @@ -13,9 +13,8 @@ from django.db.migrations.state import ( ProjectState, get_related_models_recursive, ) -from django.test import SimpleTestCase, ignore_warnings, override_settings +from django.test import SimpleTestCase, override_settings from django.test.utils import isolate_apps -from django.utils.deprecation import RemovedInDjango51Warning from .models import ( FoodManager, @@ -31,9 +30,6 @@ class StateTests(SimpleTestCase): Tests state construction, rendering and modification by operations. """ - # RemovedInDjango51Warning, when deprecation ends, only remove - # Meta.index_together from inline models. - @ignore_warnings(category=RemovedInDjango51Warning) def test_create(self): """ Tests making a ProjectState from an Apps @@ -50,7 +46,6 @@ class StateTests(SimpleTestCase): app_label = "migrations" apps = new_apps unique_together = ["name", "bio"] - index_together = ["bio", "age"] # RemovedInDjango51Warning. class AuthorProxy(Author): class Meta: @@ -142,7 +137,6 @@ class StateTests(SimpleTestCase): author_state.options, { "unique_together": {("name", "bio")}, - "index_together": {("bio", "age")}, # RemovedInDjango51Warning. "indexes": [], "constraints": [], }, diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 9f620331bc..e20b875a8e 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -54,14 +54,8 @@ from django.db.models.fields.json import KeyTextTransform from django.db.models.functions import Abs, Cast, Collate, Lower, Random, Upper from django.db.models.indexes import IndexExpression from django.db.transaction import TransactionManagementError, atomic -from django.test import ( - TransactionTestCase, - ignore_warnings, - skipIfDBFeature, - skipUnlessDBFeature, -) +from django.test import TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature from django.test.utils import CaptureQueriesContext, isolate_apps, register_lookup -from django.utils.deprecation import RemovedInDjango51Warning from .fields import CustomManyToManyField, InheritedManyToManyField, MediumBlobField from .models import ( @@ -3351,7 +3345,6 @@ class SchemaTests(TransactionTestCase): self.assertIsNone(editor.add_constraint(Author, constraint)) self.assertIsNone(editor.remove_constraint(Author, constraint)) - @ignore_warnings(category=RemovedInDjango51Warning) def test_index_together(self): """ Tests removing and adding index_together constraints on a model. @@ -3395,128 +3388,6 @@ class SchemaTests(TransactionTestCase): False, ) - @ignore_warnings(category=RemovedInDjango51Warning) - def test_index_together_with_fk(self): - """ - Tests removing and adding index_together constraints that include - a foreign key. - """ - # Create the table - with connection.schema_editor() as editor: - editor.create_model(Author) - editor.create_model(Book) - # Ensure the fields are unique to begin with - self.assertEqual(Book._meta.index_together, ()) - # Add the unique_together constraint - with connection.schema_editor() as editor: - editor.alter_index_together(Book, [], [["author", "title"]]) - # Alter it back - with connection.schema_editor() as editor: - editor.alter_index_together(Book, [["author", "title"]], []) - - @ignore_warnings(category=RemovedInDjango51Warning) - @isolate_apps("schema") - def test_create_index_together(self): - """ - Tests creating models with index_together already defined - """ - - class TagIndexed(Model): - title = CharField(max_length=255) - slug = SlugField(unique=True) - - class Meta: - app_label = "schema" - index_together = [["slug", "title"]] - - # Create the table - with connection.schema_editor() as editor: - editor.create_model(TagIndexed) - self.isolated_local_models = [TagIndexed] - # Ensure there is an index - self.assertIs( - any( - c["index"] - for c in self.get_constraints("schema_tagindexed").values() - if c["columns"] == ["slug", "title"] - ), - True, - ) - - @skipUnlessDBFeature("allows_multiple_constraints_on_same_fields") - @ignore_warnings(category=RemovedInDjango51Warning) - @isolate_apps("schema") - def test_remove_index_together_does_not_remove_meta_indexes(self): - class AuthorWithIndexedNameAndBirthday(Model): - name = CharField(max_length=255) - birthday = DateField() - - class Meta: - app_label = "schema" - index_together = [["name", "birthday"]] - - with connection.schema_editor() as editor: - editor.create_model(AuthorWithIndexedNameAndBirthday) - self.isolated_local_models = [AuthorWithIndexedNameAndBirthday] - # Add the custom index - index = Index(fields=["name", "birthday"], name="author_name_birthday_idx") - custom_index_name = index.name - AuthorWithIndexedNameAndBirthday._meta.indexes = [index] - with connection.schema_editor() as editor: - editor.add_index(AuthorWithIndexedNameAndBirthday, index) - # Ensure the indexes exist - constraints = self.get_constraints( - AuthorWithIndexedNameAndBirthday._meta.db_table - ) - self.assertIn(custom_index_name, constraints) - other_constraints = [ - name - for name, details in constraints.items() - if details["columns"] == ["name", "birthday"] - and details["index"] - and name != custom_index_name - ] - self.assertEqual(len(other_constraints), 1) - # Remove index together - index_together = AuthorWithIndexedNameAndBirthday._meta.index_together - with connection.schema_editor() as editor: - editor.alter_index_together( - AuthorWithIndexedNameAndBirthday, index_together, [] - ) - constraints = self.get_constraints( - AuthorWithIndexedNameAndBirthday._meta.db_table - ) - self.assertIn(custom_index_name, constraints) - other_constraints = [ - name - for name, details in constraints.items() - if details["columns"] == ["name", "birthday"] - and details["index"] - and name != custom_index_name - ] - self.assertEqual(len(other_constraints), 0) - # Re-add index together - with connection.schema_editor() as editor: - editor.alter_index_together( - AuthorWithIndexedNameAndBirthday, [], index_together - ) - constraints = self.get_constraints( - AuthorWithIndexedNameAndBirthday._meta.db_table - ) - self.assertIn(custom_index_name, constraints) - other_constraints = [ - name - for name, details in constraints.items() - if details["columns"] == ["name", "birthday"] - and details["index"] - and name != custom_index_name - ] - self.assertEqual(len(other_constraints), 1) - # Drop the index - with connection.schema_editor() as editor: - AuthorWithIndexedNameAndBirthday._meta.indexes = [] - editor.remove_index(AuthorWithIndexedNameAndBirthday, index) - @isolate_apps("schema") def test_db_table(self): """