1
0
mirror of https://github.com/django/django.git synced 2025-10-27 15:46:10 +00:00

Fixed #36060 -- Prevented IntegrityError in bulk_create() with order_with_respect_to.

This commit is contained in:
myoungjinGo-BE
2025-05-16 23:56:20 +09:00
committed by Sarah Boyce
parent 90429625a8
commit 953095d1e6
3 changed files with 182 additions and 2 deletions

View File

@@ -55,11 +55,15 @@ class GenericForeignKey(FieldCacheMixin, Field):
attname, column = super().get_attname_column()
return attname, None
@cached_property
def ct_field_attname(self):
return self.model._meta.get_field(self.ct_field).attname
def get_filter_kwargs_for_object(self, obj):
"""See corresponding method on Field"""
return {
self.fk_field: getattr(obj, self.fk_field),
self.ct_field: getattr(obj, self.ct_field),
self.ct_field_attname: getattr(obj, self.ct_field_attname),
}
def get_forward_related_filter(self, obj):

View File

@@ -5,6 +5,7 @@ The main QuerySet implementation. This provides the public API for the ORM.
import copy
import operator
import warnings
from functools import reduce
from itertools import chain, islice
from asgiref.sync import sync_to_async
@@ -20,7 +21,7 @@ from django.db import (
router,
transaction,
)
from django.db.models import AutoField, DateField, DateTimeField, Field, sql
from django.db.models import AutoField, DateField, DateTimeField, Field, Max, sql
from django.db.models.constants import LOOKUP_SEP, OnConflict
from django.db.models.deletion import Collector
from django.db.models.expressions import Case, DatabaseDefault, F, Value, When
@@ -800,6 +801,7 @@ class QuerySet(AltersData):
objs = list(objs)
objs_with_pk, objs_without_pk = self._prepare_for_bulk_create(objs)
with transaction.atomic(using=self.db, savepoint=False):
self._handle_order_with_respect_to(objs)
if objs_with_pk:
returned_columns = self._batched_insert(
objs_with_pk,
@@ -840,6 +842,37 @@ class QuerySet(AltersData):
return objs
def _handle_order_with_respect_to(self, objs):
if objs and (order_wrt := self.model._meta.order_with_respect_to):
get_filter_kwargs_for_object = order_wrt.get_filter_kwargs_for_object
attnames = list(get_filter_kwargs_for_object(objs[0]))
group_keys = set()
obj_groups = []
for obj in objs:
group_key = tuple(get_filter_kwargs_for_object(obj).values())
group_keys.add(group_key)
obj_groups.append((obj, group_key))
filters = [
Q.create(list(zip(attnames, group_key))) for group_key in group_keys
]
next_orders = (
self.model._base_manager.using(self.db)
.filter(reduce(operator.or_, filters))
.values_list(*attnames)
.annotate(_order__max=Max("_order") + 1)
)
# Create mapping of group values to max order.
group_next_orders = dict.fromkeys(group_keys, 0)
group_next_orders.update(
(tuple(group_key), next_order) for *group_key, next_order in next_orders
)
# Assign _order values to new objects.
for obj, group_key in obj_groups:
if getattr(obj, "_order", None) is None:
group_next_order = group_next_orders[group_key]
obj._order = group_next_order
group_next_orders[group_key] += 1
bulk_create.alters_data = True
async def abulk_create(