From d80ef32eb9e669986ed7838296f6373af56c68f0 Mon Sep 17 00:00:00 2001 From: Tim Schilling Date: Fri, 29 May 2026 14:37:29 -0500 Subject: [PATCH 1/3] Fixed #37128 -- Doc'd setUpTestData as test speed up. Revises the AnimalTestCase to better show test data isolation and avoid the opportunity to use setUpTestData. --- docs/topics/testing/overview.txt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt index 8ec1652ba35b..39a6843c0267 100644 --- a/docs/topics/testing/overview.txt +++ b/docs/topics/testing/overview.txt @@ -30,16 +30,17 @@ transaction to provide isolation:: class AnimalTestCase(TestCase): def setUp(self): - Animal.objects.create(name="lion", sound="roar") Animal.objects.create(name="cat", sound="meow") def test_animals_can_speak(self): """Animals that can speak are correctly identified""" - lion = Animal.objects.get(name="lion") cat = Animal.objects.get(name="cat") - self.assertEqual(lion.speak(), 'The lion says "roar"') self.assertEqual(cat.speak(), 'The cat says "meow"') + cat.sound = "hiss" + cat.save(update_fields=["sound"]) + self.assertEqual(cat.speak(), 'The cat says "hiss"') + When you :ref:`run your tests `, the default behavior of the test utility is to find all the test case classes (that is, subclasses of :class:`unittest.TestCase`) in any file whose name begins with ``test``, @@ -386,3 +387,12 @@ Avoiding disk access for media files The :class:`~django.core.files.storage.InMemoryStorage` is a convenient way to prevent disk access for media files. All data is kept in memory, then it gets discarded after tests run. + +Using ``setUpTestData`` to create test data +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Moving common test data to :meth:`TestCase.setUpTestData` will reduce the +number of times your tests need to create the data. Using +:meth:`TestCase.setUpTestData` to will create test data +once per class. While :meth:`TestCase.setUp() ` will +create the test data once per test. From 63ff1142b2f0cba1981825c65b62425e9ff7734b Mon Sep 17 00:00:00 2001 From: Tim Schilling Date: Tue, 2 Jun 2026 16:19:14 -0500 Subject: [PATCH 2/3] Fixed password hashing anchor link for references. --- docs/topics/testing/overview.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt index 39a6843c0267..53d32a34d384 100644 --- a/docs/topics/testing/overview.txt +++ b/docs/topics/testing/overview.txt @@ -349,7 +349,6 @@ or an unexpected success). If all the tests pass, the return code is 0. This feature is useful if you're using the test-runner script in a shell script and need to test for success or failure at that level. -.. _speeding-up-tests-auth-hashers: Speeding up the tests --------------------- @@ -360,6 +359,8 @@ Running tests in parallel As long as your tests are properly isolated, you can run them in parallel to gain a speed up on multi-core hardware. See :option:`test --parallel`. +.. _speeding-up-tests-auth-hashers: + Password hashing ~~~~~~~~~~~~~~~~ From cd385e6b8c16b51f68c1f220ff09a4cfd679af0c Mon Sep 17 00:00:00 2001 From: David Wobrock Date: Mon, 16 Mar 2026 22:39:40 +0100 Subject: [PATCH 3/3] Fixed #31317 -- Avoided crash in CreateModel with unique_together and AlterUniqueTogether. --- django/db/backends/base/schema.py | 22 +++++++- tests/migrations/test_operations.py | 85 +++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index a99218da92b2..9857eea57107 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -661,6 +661,26 @@ def _delete_composed_index(self, model, fields, constraint_kwargs, sql): } meta_index_names = {constraint.name for constraint in model._meta.indexes} columns = [model._meta.get_field(field).column for field in fields] + + # Check if the constraint is still in deferred_sql. This happens when + # CreateModel with unique_together is followed by AlterUniqueTogether + # in the same migration. index_together is not affected because its + # indexes are created immediately in CreateModel.database_forwards. + is_unique_constraint = constraint_kwargs.get("unique") is True + table = model._meta.db_table + if is_unique_constraint: + for deferred in list(self.deferred_sql): + if ( + isinstance(deferred, Statement) + and deferred.references_table(table) + and all( + deferred.references_column(table, column) for column in columns + ) + and deferred.parts["columns"].columns == columns + ): + self.deferred_sql.remove(deferred) + return + constraint_names = self._constraint_names( model, columns, @@ -668,7 +688,7 @@ def _delete_composed_index(self, model, fields, constraint_kwargs, sql): **constraint_kwargs, ) if ( - constraint_kwargs.get("unique") is True + is_unique_constraint and constraint_names and self.connection.features.allows_multiple_constraints_on_same_fields ): diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index 7189cef40063..b3d5481267aa 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -3861,6 +3861,62 @@ def test_alter_unique_together_remove(self): operation.describe(), "Alter unique_together for Pony (0 constraint(s))" ) + def test_alter_unique_together_deferred(self): + """ + AlterUniqueTogether handles deferred SQL constraints from previous + operations. Regression test for #31317. + """ + app_label = "test_aluntod" + self.apply_operations( + app_label, + ProjectState(), + operations=[ + migrations.CreateModel( + "Pony", + fields=[ + ("id", models.AutoField(primary_key=True)), + ("pink", models.IntegerField(default=3)), + ("weight", models.FloatField()), + ], + options={"unique_together": {("pink",)}}, + ), + migrations.AlterUniqueTogether( + name="Pony", + unique_together={("pink", "weight")}, + ), + ], + ) + + table_name = f"{app_label}_pony" + self.assertUniqueConstraintExists(table_name, ("pink", "weight"), value=True) + self.assertUniqueConstraintExists(table_name, ("pink",), value=False) + + def test_alter_unique_together_deferred_overlapping_columns(self): + app_label = "test_aluntodoc" + self.apply_operations( + app_label, + ProjectState(), + operations=[ + migrations.CreateModel( + "Pony", + fields=[ + ("id", models.AutoField(primary_key=True)), + ("pink", models.IntegerField(default=3)), + ("weight", models.FloatField()), + ], + options={"unique_together": [("pink", "weight"), ("pink",)]}, + ), + migrations.AlterUniqueTogether( + name="Pony", + unique_together={("pink", "weight")}, + ), + ], + ) + + table_name = f"{app_label}_pony" + self.assertUniqueConstraintExists(table_name, ("pink", "weight"), value=True) + self.assertUniqueConstraintExists(table_name, ("pink",), value=False) + @skipUnlessDBFeature("allows_multiple_constraints_on_same_fields") def test_remove_unique_together_on_pk_field(self): app_label = "test_rutopkf" @@ -4452,6 +4508,35 @@ def test_alter_index_together_remove_with_unique_together(self): self.assertIndexNotExists(table_name, ["pink", "weight"]) self.assertUniqueConstraintExists(table_name, ["pink", "weight"]) + def test_alter_index_together_deferred_overlapping_columns(self): + app_label = "test_alintodoc" + self.apply_operations( + app_label, + ProjectState(), + operations=[ + migrations.CreateModel( + "Pony", + fields=[ + ("id", models.AutoField(primary_key=True)), + ("pink", models.IntegerField(default=3)), + ("weight", models.FloatField()), + ], + options={ + "unique_together": [("pink",)], + "index_together": [("pink",)], + }, + ), + migrations.AlterIndexTogether( + name="Pony", + index_together=set(), + ), + ], + ) + + table_name = f"{app_label}_pony" + self.assertIndexNotExists(table_name, ["pink"]) + self.assertUniqueConstraintExists(table_name, ("pink",), value=True) + def test_add_constraint(self): project_state = self.set_up_test_model("test_addconstraint") gt_check = models.Q(pink__gt=2)