django-bulk-hooks 0.2.51__tar.gz → 0.2.53__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.51 → django_bulk_hooks-0.2.53}/PKG-INFO +1 -1
  2. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/operations/bulk_executor.py +17 -28
  3. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/operations/mti_handler.py +12 -13
  4. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/pyproject.toml +1 -1
  5. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/LICENSE +0 -0
  6. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/README.md +0 -0
  7. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/__init__.py +0 -0
  8. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/changeset.py +0 -0
  9. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/conditions.py +0 -0
  10. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/constants.py +0 -0
  11. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/context.py +0 -0
  12. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/decorators.py +0 -0
  13. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/dispatcher.py +0 -0
  14. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/enums.py +0 -0
  15. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/factory.py +0 -0
  16. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/handler.py +0 -0
  17. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/helpers.py +0 -0
  18. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/manager.py +0 -0
  19. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/models.py +0 -0
  20. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/operations/__init__.py +0 -0
  21. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/operations/analyzer.py +0 -0
  22. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/operations/coordinator.py +0 -0
  23. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/operations/field_utils.py +0 -0
  24. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/operations/mti_plans.py +0 -0
  25. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/operations/record_classifier.py +0 -0
  26. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/django_bulk_hooks/queryset.py +0 -0
  27. {django_bulk_hooks-0.2.51 → django_bulk_hooks-0.2.53}/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.51
3
+ Version: 0.2.53
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
@@ -261,57 +261,46 @@ class BulkExecutor:
261
261
  parent_instances_map[orig_obj_id] = {}
262
262
  parent_instances_map[orig_obj_id][parent_level.model_class] = parent_obj
263
263
 
264
- # Step 2: Add parent links to child objects and set PKs for existing records
264
+ # Step 2: Add parent links to child objects and set PKs appropriately
265
265
  for child_obj, orig_obj in zip(plan.child_objects, plan.original_objects):
266
266
  parent_instances = parent_instances_map.get(id(orig_obj), {})
267
267
 
268
- # Set parent links
268
+ # Set parent links and PKs for all objects (since in MTI, child PK = parent PK)
269
269
  for parent_model, parent_instance in parent_instances.items():
270
270
  parent_link = plan.child_model._meta.get_ancestor_link(parent_model)
271
271
  if parent_link:
272
- setattr(child_obj, parent_link.attname, parent_instance.pk)
272
+ parent_pk = parent_instance.pk
273
+ setattr(child_obj, parent_link.attname, parent_pk)
273
274
  setattr(child_obj, parent_link.name, parent_instance)
274
- # IMPORTANT: Don't set the child's PK here - it should only get PK after insertion
275
- # The parent link field (hookmodel_ptr) is NOT the same as the child's PK
275
+ # In MTI, the child PK IS the parent link
276
+ child_obj.pk = parent_pk
277
+ child_obj.id = parent_pk
276
278
  else:
277
279
  logger.warning(f"No parent link found for {parent_model} in {plan.child_model}")
278
280
 
279
- # For existing records in upsert, ensure PK is set on child object
280
- if id(orig_obj) in plan.existing_record_ids:
281
- pk_value = getattr(orig_obj, "pk", None)
282
- if pk_value:
283
- child_obj.pk = pk_value
284
- child_obj.id = pk_value
285
- else:
286
- # If no PK on original object, this is a new record, don't set PK
287
- logger.info(f"New record {orig_obj} - not setting PK on child object")
288
-
289
281
  # Step 3: Handle child objects
290
282
  # Note: We can't use bulk_create on child MTI models, so we use _batched_insert for new records
291
283
  # and bulk_update for existing records
292
284
  base_qs = BaseQuerySet(model=plan.child_model, using=self.queryset.db)
293
-
294
- # For MTI child objects, we need to handle them differently
295
- # In MTI, child objects get PKs from parent links, but we need to distinguish
296
- # between truly new records and existing records for upsert operations
285
+
286
+ # For MTI child objects, we need to distinguish between truly new records and existing records for upsert operations
297
287
  objs_without_pk, objs_with_pk = [], []
298
-
288
+
299
289
  # Check which CHILD records actually exist in the child table
300
- # This is separate from checking parent existence
301
290
  if plan.update_conflicts:
302
- # Query the CHILD table to see which child records exist
303
- parent_pks = []
291
+ # For upsert, check which child records exist based on the parent PKs
292
+ parent_pks_to_check = []
304
293
  for child_obj in plan.child_objects:
305
294
  child_pk = getattr(child_obj, plan.child_model._meta.pk.attname, None)
306
295
  if child_pk:
307
- parent_pks.append(child_pk)
308
-
296
+ parent_pks_to_check.append(child_pk)
297
+
309
298
  existing_child_pks = set()
310
- if parent_pks:
299
+ if parent_pks_to_check:
311
300
  existing_child_pks = set(
312
- base_qs.filter(pk__in=parent_pks).values_list('pk', flat=True)
301
+ base_qs.filter(pk__in=parent_pks_to_check).values_list('pk', flat=True)
313
302
  )
314
-
303
+
315
304
  # Split based on whether child record exists
316
305
  for child_obj in plan.child_objects:
317
306
  child_pk = getattr(child_obj, plan.child_model._meta.pk.attname, None)
@@ -121,26 +121,29 @@ class MTIHandler:
121
121
  - If parent exists but child doesn't: creating child for existing parent → AFTER_UPDATE
122
122
  - If neither exists: creating both parent and child → AFTER_CREATE
123
123
 
124
- Therefore, we return the root parent model to check if the parent record exists,
125
- regardless of where the unique fields are defined.
124
+ Therefore, we find the model that contains all the unique fields, regardless
125
+ of whether it's the parent or child model.
126
126
 
127
127
  Args:
128
128
  unique_fields: List of field names forming the unique constraint
129
129
 
130
130
  Returns:
131
- Model class to query for existing records (root parent for MTI)
131
+ Model class to query for existing records (model containing unique fields)
132
132
  """
133
133
  if not unique_fields:
134
134
  return self.model_cls
135
135
 
136
136
  inheritance_chain = self.get_inheritance_chain()
137
137
 
138
- # For MTI models with multiple levels, return the root parent model
139
- # This ensures we check if the parent exists, which determines create vs update hooks
138
+ # For MTI models, find the model in the chain that contains ALL unique fields
140
139
  if len(inheritance_chain) > 1:
141
- return inheritance_chain[0] # Root parent model
140
+ # Walk through inheritance chain from child to parent
141
+ for model in reversed(inheritance_chain): # Start with child, end with root parent
142
+ model_field_names = {f.name for f in model._meta.local_fields}
143
+ if all(field in model_field_names for field in unique_fields):
144
+ return model
142
145
 
143
- # For non-MTI models (shouldn't happen, but safe fallback)
146
+ # For non-MTI models or as fallback
144
147
  return self.model_cls
145
148
 
146
149
  # ==================== MTI BULK CREATE PLANNING ====================
@@ -190,12 +193,8 @@ class MTIHandler:
190
193
  if existing_pks_map is None:
191
194
  existing_pks_map = {}
192
195
 
193
- # Set PKs on existing objects so they can be updated
194
- if existing_pks_map:
195
- for obj in objs:
196
- if id(obj) in existing_pks_map:
197
- obj.pk = existing_pks_map[id(obj)]
198
- obj.id = existing_pks_map[id(obj)]
196
+ # For upsert operations, don't set PKs on source objects - let Django's bulk_create handle it
197
+ # The PKs will be set on the resulting objects after the bulk operation
199
198
 
200
199
  # Build parent levels
201
200
  parent_levels = self._build_parent_levels(
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-bulk-hooks"
3
- version = "0.2.51"
3
+ version = "0.2.53"
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"