django-bulk-hooks 0.2.56__py3-none-any.whl → 0.2.57__py3-none-any.whl

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.

@@ -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):
@@ -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 = [f.name for f in model_class._meta.local_fields
331
- if not isinstance(f, AutoField) and f.name in model_fields_by_name]
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-bulk-hooks
3
- Version: 0.2.56
3
+ Version: 0.2.57
4
4
  Summary: Hook-style hooks for Django bulk operations like bulk_create and bulk_update.
5
5
  License: MIT
6
6
  Keywords: django,bulk,hooks
@@ -13,15 +13,15 @@ django_bulk_hooks/manager.py,sha256=3mFzB0ZzHHeXWdKGObZD_H0NlskHJc8uYBF69KKdAXU,
13
13
  django_bulk_hooks/models.py,sha256=4Vvi2LiGP0g4j08a5liqBROfsO8Wd_ermBoyjKwfrPU,2512
14
14
  django_bulk_hooks/operations/__init__.py,sha256=BtJYjmRhe_sScivLsniDaZmBkm0ZLvcmzXFKL7QY2Xg,550
15
15
  django_bulk_hooks/operations/analyzer.py,sha256=wAG8sAG9NwfwNqG9z81VfGR7AANDzRmMGE_o82MWji4,10689
16
- django_bulk_hooks/operations/bulk_executor.py,sha256=tMbcwQSN8xNoKL7-KC8m-XhGyqwaAtAQ5CAm3PYHl_Q,24122
16
+ django_bulk_hooks/operations/bulk_executor.py,sha256=Y-wkvuV_X-SZmI965JVrrtwbzPZVggUfy8mR1pzP9d0,27048
17
17
  django_bulk_hooks/operations/coordinator.py,sha256=iGavJLqe3eYRqFay8cMn6muwyRYzQo-HFGphsS5hL6g,30799
18
18
  django_bulk_hooks/operations/field_utils.py,sha256=Tvr5bcZLG8imH-r2S85oui1Cbw6hGv3VtuIMn4OvsU4,2895
19
- django_bulk_hooks/operations/mti_handler.py,sha256=l1BPeqK1eTjloMxBPZPVAaWyBK14j1jUFVELdl-GF1E,23441
19
+ django_bulk_hooks/operations/mti_handler.py,sha256=MMDxuE6jzwkhMwVE-6qJUdp4MNKgAIFPMjwdN_umrqs,23503
20
20
  django_bulk_hooks/operations/mti_plans.py,sha256=7STQ2oA2ZT8cEG3-t-6xciRAdf7OeSf0gRLXR_BRG-Q,3363
21
21
  django_bulk_hooks/operations/record_classifier.py,sha256=kqML4aO11X9K3SSJ5DUlUukwI172j_Tk12Kr77ee8q8,7065
22
22
  django_bulk_hooks/queryset.py,sha256=aQitlbexcVnmeAdc0jtO3hci39p4QEu4srQPEzozy5s,5546
23
23
  django_bulk_hooks/registry.py,sha256=uum5jhGI3TPaoiXuA1MdBdu4gbE3rQGGwQ5YDjiMcjk,7949
24
- django_bulk_hooks-0.2.56.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
25
- django_bulk_hooks-0.2.56.dist-info/METADATA,sha256=0r_GZFFTjdK1IJBe3iSsuSgSky38xbyMLzbA4CAy150,9265
26
- django_bulk_hooks-0.2.56.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
27
- django_bulk_hooks-0.2.56.dist-info/RECORD,,
24
+ django_bulk_hooks-0.2.57.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
25
+ django_bulk_hooks-0.2.57.dist-info/METADATA,sha256=SWZOMPOgiBDCLuSxHX9xTeJMqcd3CgvDZP-JuoY4wRs,9265
26
+ django_bulk_hooks-0.2.57.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
27
+ django_bulk_hooks-0.2.57.dist-info/RECORD,,