diff --git a/django/db/models/query.py b/django/db/models/query.py index 2d9bcee9bc96..748a0508b2ee 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -747,6 +747,7 @@ async def acreate(self, **kwargs): def _prepare_for_bulk_create(self, objs): objs_with_pk, objs_without_pk = [], [] for obj in objs: + obj._prepare_related_fields_for_save(operation_name="bulk_create") if isinstance(obj.pk, DatabaseDefault): objs_without_pk.append(obj) elif obj._is_pk_set(): @@ -757,7 +758,6 @@ def _prepare_for_bulk_create(self, objs): objs_with_pk.append(obj) else: objs_without_pk.append(obj) - obj._prepare_related_fields_for_save(operation_name="bulk_create") return objs_with_pk, objs_without_pk def _check_bulk_create_options( diff --git a/docs/releases/6.0.8.txt b/docs/releases/6.0.8.txt index 4f05b218da02..8ce8e50f5308 100644 --- a/docs/releases/6.0.8.txt +++ b/docs/releases/6.0.8.txt @@ -11,4 +11,7 @@ bugs in 6.0.7. Bugfixes ======== -* ... +* Fixed a regression in Django 6.0 that caused + :meth:`~django.db.models.query.QuerySet.bulk_create` to crash on databases + that support returning rows from bulk inserts when a related object providing + the primary key was saved after assignment (:ticket:`37234`). diff --git a/tests/basic/tests.py b/tests/basic/tests.py index a81724b70f51..d8f95b43aac7 100644 --- a/tests/basic/tests.py +++ b/tests/basic/tests.py @@ -1099,3 +1099,24 @@ def test_refresh_overwrites_queryset_fields(self): with self.assertNumQueries(1): a.refresh_from_db(fields=["headline"], from_queryset=from_queryset) self.assertEqual(a.headline, headline) + + def test_refresh_copies_fetch_mode_from_plucked_instance(self): + a = Article.objects.create(pub_date=datetime.now()) + fa = FeaturedArticle.objects.fetch_mode(models.FETCH_PEERS).create(article=a) + + from_queryset = FeaturedArticle.objects.fetch_mode(models.RAISE).select_related( + "article" + ) + fa.refresh_from_db(from_queryset=from_queryset) + self.assertEqual(fa._state.fetch_mode, models.FETCH_PEERS) + self.assertEqual(fa.article._state.fetch_mode, models.RAISE) + + def test_refresh_ignores_fetch_mode_if_no_instance_plucked(self): + a = Article.objects.create(pub_date=datetime.now()) + fa = FeaturedArticle.objects.fetch_mode(models.FETCH_PEERS).create(article=a) + + # This queryset's fetch mode is not used because no fields are plucked. + from_queryset = FeaturedArticle.objects.fetch_mode(models.RAISE) + fa.refresh_from_db(from_queryset=from_queryset) + self.assertEqual(fa._state.fetch_mode, models.FETCH_PEERS) + self.assertEqual(fa.article._state.fetch_mode, models.FETCH_PEERS) diff --git a/tests/bulk_create/tests.py b/tests/bulk_create/tests.py index 397fcb9186c3..eaf4bc05083c 100644 --- a/tests/bulk_create/tests.py +++ b/tests/bulk_create/tests.py @@ -439,6 +439,13 @@ def test_nullable_fk_after_parent_bulk_create(self): child = NullableFields.objects.get(integer_field=88) self.assertEqual(child.auto_field, parent) + def test_pk_from_related_instance_saved_after_init(self): + country = Country(name="Syldavia", iso_two_letter="SW") + related = RelatedModel(country=country) + country.save() + RelatedModel.objects.bulk_create([related]) + self.assertEqual(related.country_id, country.pk) + def test_unsaved_parent(self): parent = NoFields() msg = ( diff --git a/tests/test_client/tests.py b/tests/test_client/tests.py index 98f01827d0a7..cc66a157b0a5 100644 --- a/tests/test_client/tests.py +++ b/tests/test_client/tests.py @@ -908,6 +908,16 @@ def test_redirect_with_http_host(self): ) self.assertEqual(response.content, b"hostname2") + @override_settings(PREPEND_WWW=True, ALLOWED_HOSTS=["testserver", "www.testserver"]) + def test_prepend_www_follow_redirect(self): + response = self.client.get("/get_view/", follow=True) + self.assertRedirects( + response, + "http://www.testserver/get_view/", + status_code=301, + target_status_code=200, + ) + def test_external_redirect_without_trailing_slash(self): """ Client._handle_redirects() with an empty path.