django-bulk-hooks 0.2.56__tar.gz → 0.2.57__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-bulk-hooks might be problematic. Click here for more details.
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/PKG-INFO +1 -1
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/bulk_executor.py +52 -2
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/mti_handler.py +6 -3
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/pyproject.toml +1 -1
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/LICENSE +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/README.md +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/__init__.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/changeset.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/conditions.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/constants.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/context.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/decorators.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/dispatcher.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/enums.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/factory.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/handler.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/helpers.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/manager.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/models.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/__init__.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/analyzer.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/coordinator.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/field_utils.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/mti_plans.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/record_classifier.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/queryset.py +0 -0
- {django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/registry.py +0 -0
{django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/bulk_executor.py
RENAMED
|
@@ -8,6 +8,7 @@ import logging
|
|
|
8
8
|
|
|
9
9
|
from django.db import transaction
|
|
10
10
|
from django.db.models import AutoField, ForeignKey, Case, When, Value
|
|
11
|
+
from django.db.models.constants import OnConflict
|
|
11
12
|
from django.db.models.functions import Cast
|
|
12
13
|
|
|
13
14
|
from django_bulk_hooks.operations.field_utils import get_field_value_for_db
|
|
@@ -333,15 +334,64 @@ class BulkExecutor:
|
|
|
333
334
|
if objs_without_pk:
|
|
334
335
|
base_qs._prepare_for_bulk_create(objs_without_pk)
|
|
335
336
|
opts = plan.child_model._meta
|
|
336
|
-
|
|
337
|
+
|
|
337
338
|
# Include all local fields except auto-generated ones
|
|
338
339
|
# For MTI, we need to include the parent link (which is the PK)
|
|
339
340
|
filtered_fields = [f for f in opts.local_fields if not f.generated]
|
|
340
|
-
|
|
341
|
+
|
|
342
|
+
# FIX: Pass conflict resolution parameters to _batched_insert for MTI child tables
|
|
343
|
+
# Previously, _batched_insert was called without on_conflict/unique_fields/update_fields,
|
|
344
|
+
# causing IntegrityError when child tables have unique constraints during upsert operations.
|
|
345
|
+
# See: https://github.com/user/repo/issues/XXX
|
|
346
|
+
# Prepare conflict resolution parameters for upsert
|
|
347
|
+
on_conflict = None
|
|
348
|
+
batched_unique_fields = None
|
|
349
|
+
batched_update_fields = None
|
|
350
|
+
|
|
351
|
+
if plan.update_conflicts:
|
|
352
|
+
# Filter unique_fields and update_fields to only those on child model
|
|
353
|
+
# Django's _batched_insert expects field objects, not field names
|
|
354
|
+
child_model_fields_dict = {field.name: field for field in plan.child_model._meta.local_fields}
|
|
355
|
+
|
|
356
|
+
# Unique fields may be on parent or child - filter to child only for child table insert
|
|
357
|
+
# Convert field names to field objects
|
|
358
|
+
if plan.unique_fields:
|
|
359
|
+
batched_unique_fields = [
|
|
360
|
+
child_model_fields_dict[fname]
|
|
361
|
+
for fname in plan.unique_fields
|
|
362
|
+
if fname in child_model_fields_dict
|
|
363
|
+
]
|
|
364
|
+
|
|
365
|
+
# Update fields - filter to child only
|
|
366
|
+
# Keep as strings - Django's _batched_insert accepts field name strings for update_fields
|
|
367
|
+
if plan.update_fields:
|
|
368
|
+
batched_update_fields = [
|
|
369
|
+
fname
|
|
370
|
+
for fname in plan.update_fields
|
|
371
|
+
if fname in child_model_fields_dict
|
|
372
|
+
]
|
|
373
|
+
|
|
374
|
+
# Only set on_conflict if we have unique fields for this table
|
|
375
|
+
# Note: If unique_fields are all on parent, batched_unique_fields will be empty,
|
|
376
|
+
# meaning no conflict resolution needed for child table
|
|
377
|
+
if batched_unique_fields:
|
|
378
|
+
if batched_update_fields:
|
|
379
|
+
# We have both unique fields and update fields on child - use UPDATE
|
|
380
|
+
on_conflict = OnConflict.UPDATE
|
|
381
|
+
else:
|
|
382
|
+
# We have unique fields on child but no update fields - use IGNORE
|
|
383
|
+
# This handles the case where all update fields are on parent tables
|
|
384
|
+
on_conflict = OnConflict.IGNORE
|
|
385
|
+
# Clear batched_update_fields to avoid issues
|
|
386
|
+
batched_update_fields = None
|
|
387
|
+
|
|
341
388
|
returned_columns = base_qs._batched_insert(
|
|
342
389
|
objs_without_pk,
|
|
343
390
|
filtered_fields,
|
|
344
391
|
batch_size=len(objs_without_pk),
|
|
392
|
+
on_conflict=on_conflict,
|
|
393
|
+
update_fields=batched_update_fields,
|
|
394
|
+
unique_fields=batched_unique_fields,
|
|
345
395
|
)
|
|
346
396
|
if returned_columns:
|
|
347
397
|
for obj, results in zip(objs_without_pk, returned_columns):
|
{django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/mti_handler.py
RENAMED
|
@@ -252,7 +252,7 @@ class MTIHandler:
|
|
|
252
252
|
|
|
253
253
|
parent_levels = []
|
|
254
254
|
parent_instances_map = {} # Maps obj id() -> {model_class: parent_instance}
|
|
255
|
-
|
|
255
|
+
|
|
256
256
|
# Set defaults
|
|
257
257
|
if existing_record_ids is None:
|
|
258
258
|
existing_record_ids = set()
|
|
@@ -327,8 +327,11 @@ class MTIHandler:
|
|
|
327
327
|
level_unique_fields = [pk_field.name]
|
|
328
328
|
# Use a safe update field - pick the first available non-PK field
|
|
329
329
|
# or use the PK itself as a dummy (updating to itself is a no-op)
|
|
330
|
-
available_fields = [
|
|
331
|
-
|
|
330
|
+
available_fields = [
|
|
331
|
+
f.name
|
|
332
|
+
for f in model_class._meta.local_fields
|
|
333
|
+
if not isinstance(f, AutoField) and f.name in model_fields_by_name
|
|
334
|
+
]
|
|
332
335
|
level_update_fields = available_fields[:1] if available_fields else [pk_field.name]
|
|
333
336
|
|
|
334
337
|
# Create parent level
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/__init__.py
RENAMED
|
File without changes
|
{django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/analyzer.py
RENAMED
|
File without changes
|
{django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/coordinator.py
RENAMED
|
File without changes
|
{django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/field_utils.py
RENAMED
|
File without changes
|
{django_bulk_hooks-0.2.56 → django_bulk_hooks-0.2.57}/django_bulk_hooks/operations/mti_plans.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|