django-bulk-hooks 0.2.54__tar.gz → 0.2.56__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.

Files changed (27) hide show
  1. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/PKG-INFO +1 -1
  2. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/operations/mti_handler.py +36 -0
  3. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/pyproject.toml +1 -1
  4. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/LICENSE +0 -0
  5. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/README.md +0 -0
  6. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/__init__.py +0 -0
  7. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/changeset.py +0 -0
  8. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/conditions.py +0 -0
  9. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/constants.py +0 -0
  10. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/context.py +0 -0
  11. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/decorators.py +0 -0
  12. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/dispatcher.py +0 -0
  13. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/enums.py +0 -0
  14. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/factory.py +0 -0
  15. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/handler.py +0 -0
  16. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/helpers.py +0 -0
  17. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/manager.py +0 -0
  18. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/models.py +0 -0
  19. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/operations/__init__.py +0 -0
  20. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/operations/analyzer.py +0 -0
  21. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/operations/bulk_executor.py +0 -0
  22. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/operations/coordinator.py +0 -0
  23. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/operations/field_utils.py +0 -0
  24. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/operations/mti_plans.py +0 -0
  25. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/operations/record_classifier.py +0 -0
  26. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/queryset.py +0 -0
  27. {django_bulk_hooks-0.2.54 → django_bulk_hooks-0.2.56}/django_bulk_hooks/registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-bulk-hooks
3
- Version: 0.2.54
3
+ Version: 0.2.56
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
@@ -207,6 +207,8 @@ class MTIHandler:
207
207
  update_conflicts=update_conflicts,
208
208
  unique_fields=unique_fields,
209
209
  update_fields=update_fields,
210
+ existing_record_ids=existing_record_ids,
211
+ existing_pks_map=existing_pks_map,
210
212
  )
211
213
 
212
214
  # Build child object templates (without parent links - executor adds them)
@@ -235,6 +237,8 @@ class MTIHandler:
235
237
  update_conflicts=False,
236
238
  unique_fields=None,
237
239
  update_fields=None,
240
+ existing_record_ids=None,
241
+ existing_pks_map=None,
238
242
  ):
239
243
  """
240
244
  Build parent level objects for each level in the inheritance chain.
@@ -248,6 +252,12 @@ class MTIHandler:
248
252
 
249
253
  parent_levels = []
250
254
  parent_instances_map = {} # Maps obj id() -> {model_class: parent_instance}
255
+
256
+ # Set defaults
257
+ if existing_record_ids is None:
258
+ existing_record_ids = set()
259
+ if existing_pks_map is None:
260
+ existing_pks_map = {}
251
261
 
252
262
  for level_idx, model_class in enumerate(inheritance_chain[:-1]):
253
263
  parent_objs_for_level = []
@@ -301,6 +311,25 @@ class MTIHandler:
301
311
  level_update_conflicts = True
302
312
  level_unique_fields = normalized_unique
303
313
  level_update_fields = filtered_updates
314
+ else:
315
+ # CRITICAL FIX: Even if this parent level doesn't have the unique constraint,
316
+ # we still need update_conflicts=True during MTI upsert. Without it, parent
317
+ # does plain INSERT creating a new parent record with a new PK, breaking the
318
+ # MTI relationship and causing child INSERT to fail on its unique constraint.
319
+ #
320
+ # Solution: Use the primary key as the unique field for parent levels.
321
+ # In MTI upsert with existing_pks_map, parent objects will get their PKs from
322
+ # bulk_create, and we need those PKs to match existing records, not create new ones.
323
+ if existing_record_ids and existing_pks_map:
324
+ # Use primary key for upsert matching at this level
325
+ pk_field = model_class._meta.pk
326
+ level_update_conflicts = True
327
+ level_unique_fields = [pk_field.name]
328
+ # Use a safe update field - pick the first available non-PK field
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]
332
+ level_update_fields = available_fields[:1] if available_fields else [pk_field.name]
304
333
 
305
334
  # Create parent level
306
335
  parent_level = ParentLevel(
@@ -361,6 +390,13 @@ class MTIHandler:
361
390
 
362
391
  # Copy field values from source using centralized field extraction
363
392
  for field in parent_model._meta.local_fields:
393
+ # Skip AutoField (primary key) - let Django's bulk_create with update_conflicts
394
+ # handle PK assignment based on unique_fields. Setting PKs manually here causes
395
+ # conflicts when parent records exist but update_conflicts isn't properly configured
396
+ # for this level, leading to IntegrityError on primary key constraint.
397
+ if isinstance(field, AutoField):
398
+ continue
399
+
364
400
  if hasattr(source_obj, field.name):
365
401
  # Use centralized field value extraction for consistent FK handling
366
402
  value = get_field_value_for_db(source_obj, field.name, source_obj.__class__)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-bulk-hooks"
3
- version = "0.2.54"
3
+ version = "0.2.56"
4
4
  description = "Hook-style hooks for Django bulk operations like bulk_create and bulk_update."
5
5
  authors = ["Konrad Beck <konrad.beck@merchantcapital.co.za>"]
6
6
  readme = "README.md"