1
0
mirror of https://github.com/django/django.git synced 2025-05-23 07:16:29 +00:00

Fixed #36260 -- Made bulk_create() work with DB-generated primary keys.

Co-authored-by: Simon Charette <charette.s@gmail.com>
This commit is contained in:
Dmitry Shachnev 2025-03-16 20:50:45 +03:00 committed by Sarah Boyce
parent c75fbe8430
commit 77b4ecbd53
4 changed files with 21 additions and 6 deletions

View File

@ -23,7 +23,7 @@ from django.db import (
from django.db.models import AutoField, DateField, DateTimeField, Field, sql from django.db.models import AutoField, DateField, DateTimeField, Field, sql
from django.db.models.constants import LOOKUP_SEP, OnConflict from django.db.models.constants import LOOKUP_SEP, OnConflict
from django.db.models.deletion import Collector from django.db.models.deletion import Collector
from django.db.models.expressions import Case, F, Value, When from django.db.models.expressions import Case, DatabaseDefault, F, Value, When
from django.db.models.functions import Cast, Trunc from django.db.models.functions import Cast, Trunc
from django.db.models.query_utils import FilteredRelation, Q from django.db.models.query_utils import FilteredRelation, Q
from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE, ROW_COUNT from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE, ROW_COUNT
@ -789,7 +789,10 @@ class QuerySet(AltersData):
objs = list(objs) objs = list(objs)
self._prepare_for_bulk_create(objs) self._prepare_for_bulk_create(objs)
with transaction.atomic(using=self.db, savepoint=False): with transaction.atomic(using=self.db, savepoint=False):
objs_without_pk, objs_with_pk = partition(lambda o: o._is_pk_set(), objs) objs_without_pk, objs_with_pk = partition(
lambda o: o._is_pk_set() and not isinstance(o.pk, DatabaseDefault),
objs,
)
if objs_with_pk: if objs_with_pk:
returned_columns = self._batched_insert( returned_columns = self._batched_insert(
objs_with_pk, objs_with_pk,

View File

@ -2427,10 +2427,11 @@ This has a number of caveats though:
* The model's ``save()`` method will not be called, and the ``pre_save`` and * The model's ``save()`` method will not be called, and the ``pre_save`` and
``post_save`` signals will not be sent. ``post_save`` signals will not be sent.
* It does not work with child models in a multi-table inheritance scenario. * It does not work with child models in a multi-table inheritance scenario.
* If the model's primary key is an :class:`~django.db.models.AutoField` and * If the model's primary key is an :class:`~django.db.models.AutoField` or has
``ignore_conflicts`` is False, the primary key attribute can only be a :attr:`~django.db.models.Field.db_default` value, and ``ignore_conflicts``
retrieved on certain databases (currently PostgreSQL, MariaDB, and SQLite is ``False``, the primary key attribute can only be retrieved on certain
3.35+). On other databases, it will not be set. databases (currently PostgreSQL, MariaDB, and SQLite 3.35+). On other
databases, it will not be set.
* It does not work with many-to-many relationships. * It does not work with many-to-many relationships.
* It casts ``objs`` to a list, which fully evaluates ``objs`` if it's a * It casts ``objs`` to a list, which fully evaluates ``objs`` if it's a
generator. The cast allows inspecting all objects so that any objects with a generator. The cast allows inspecting all objects so that any objects with a

View File

@ -147,3 +147,7 @@ class RelatedModel(models.Model):
class DbDefaultModel(models.Model): class DbDefaultModel(models.Model):
name = models.CharField(max_length=10) name = models.CharField(max_length=10)
created_at = models.DateTimeField(db_default=Now()) created_at = models.DateTimeField(db_default=Now())
class DbDefaultPrimaryKey(models.Model):
id = models.DateTimeField(primary_key=True, db_default=Now())

View File

@ -1,3 +1,4 @@
from datetime import datetime
from math import ceil from math import ceil
from operator import attrgetter from operator import attrgetter
@ -23,6 +24,7 @@ from .models import (
BigAutoFieldModel, BigAutoFieldModel,
Country, Country,
DbDefaultModel, DbDefaultModel,
DbDefaultPrimaryKey,
FieldsWithDbColumns, FieldsWithDbColumns,
NoFields, NoFields,
NullableFields, NullableFields,
@ -866,3 +868,8 @@ class BulkCreateTests(TestCase):
ctx[0]["sql"].count(created_at_quoted_name), ctx[0]["sql"].count(created_at_quoted_name),
2 if connection.features.can_return_rows_from_bulk_insert else 1, 2 if connection.features.can_return_rows_from_bulk_insert else 1,
) )
@skipUnlessDBFeature("can_return_rows_from_bulk_insert")
def test_db_default_primary_key(self):
(obj,) = DbDefaultPrimaryKey.objects.bulk_create([DbDefaultPrimaryKey()])
self.assertIsInstance(obj.id, datetime)