Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion django/db/backends/base/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,14 +661,34 @@ 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,
exclude=meta_constraint_names | meta_index_names,
**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
):
Expand Down
19 changes: 15 additions & 4 deletions docs/topics/testing/overview.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 <running-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``,
Expand Down Expand Up @@ -348,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
---------------------
Expand All @@ -359,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
~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -386,3 +388,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() <unittest.TestCase.setUp>` will
create the test data once per test.
85 changes: 85 additions & 0 deletions tests/migrations/test_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
Loading