django-bulk-hooks 0.1.146__tar.gz → 0.1.148__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 (17) hide show
  1. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/PKG-INFO +1 -1
  2. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/django_bulk_hooks/queryset.py +18 -47
  3. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/pyproject.toml +1 -1
  4. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/LICENSE +0 -0
  5. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/README.md +0 -0
  6. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/django_bulk_hooks/__init__.py +0 -0
  7. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/django_bulk_hooks/conditions.py +0 -0
  8. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/django_bulk_hooks/constants.py +0 -0
  9. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/django_bulk_hooks/context.py +0 -0
  10. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/django_bulk_hooks/decorators.py +0 -0
  11. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/django_bulk_hooks/engine.py +0 -0
  12. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/django_bulk_hooks/enums.py +0 -0
  13. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/django_bulk_hooks/handler.py +0 -0
  14. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/django_bulk_hooks/manager.py +0 -0
  15. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/django_bulk_hooks/models.py +0 -0
  16. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/django_bulk_hooks/priority.py +0 -0
  17. {django_bulk_hooks-0.1.146 → django_bulk_hooks-0.1.148}/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.1.146
3
+ Version: 0.1.148
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
@@ -97,15 +97,10 @@ class HookQuerySet(models.QuerySet):
97
97
  # MultiTableParent -> ProxyChild. Simply checking self.model._meta.proxy would not
98
98
  # identify that case as involving multiple tables.
99
99
  is_mti = False
100
- print(f"DEBUG: Checking for MTI on {model_cls.__name__}")
101
- print(f"DEBUG: All parents: {[p.__name__ for p in model_cls._meta.all_parents]}")
102
100
  for parent in model_cls._meta.all_parents:
103
- print(f"DEBUG: Checking parent {parent.__name__} (concrete: {parent._meta.concrete_model.__name__}) vs {model_cls.__name__} (concrete: {model_cls._meta.concrete_model.__name__})")
104
101
  if parent._meta.concrete_model is not model_cls._meta.concrete_model:
105
102
  is_mti = True
106
- print(f"DEBUG: MTI detected! {parent.__name__} and {model_cls.__name__} have different concrete models")
107
103
  break
108
- print(f"DEBUG: Final MTI result: {is_mti}")
109
104
 
110
105
  if not objs:
111
106
  return objs
@@ -124,7 +119,6 @@ class HookQuerySet(models.QuerySet):
124
119
 
125
120
  # For MTI models, we need to handle them specially
126
121
  if is_mti:
127
- print(f"DEBUG: Using MTI-specific logic for {model_cls.__name__}")
128
122
  # Use our MTI-specific logic
129
123
  result = self._mti_bulk_create(
130
124
  objs,
@@ -135,7 +129,6 @@ class HookQuerySet(models.QuerySet):
135
129
  unique_fields=unique_fields,
136
130
  )
137
131
  else:
138
- print(f"DEBUG: Using standard bulk_create for {model_cls.__name__}")
139
132
  # For single-table models, use Django's built-in bulk_create
140
133
  # but we need to call it on the base manager to avoid recursion
141
134
 
@@ -332,16 +325,25 @@ class HookQuerySet(models.QuerySet):
332
325
  parent_objects_map = {}
333
326
 
334
327
  # Step 1: Do O(n) normal inserts into parent tables to get primary keys back
335
- print(f"DEBUG: Processing batch of {len(batch)} objects")
336
- print(f"DEBUG: Inheritance chain: {[m.__name__ for m in inheritance_chain]}")
328
+ # Get bypass_hooks from kwargs
329
+ bypass_hooks = kwargs.get('bypass_hooks', False)
330
+ bypass_validation = kwargs.get('bypass_validation', False)
331
+
337
332
  for obj in batch:
338
333
  parent_instances = {}
339
334
  current_parent = None
340
335
  for model_class in inheritance_chain[:-1]:
341
- print(f"DEBUG: Creating parent instance for {model_class.__name__}")
342
336
  parent_obj = self._create_parent_instance(
343
337
  obj, model_class, current_parent
344
338
  )
339
+
340
+ # Fire parent hooks if not bypassed
341
+ if not bypass_hooks:
342
+ ctx = HookContext(model_class)
343
+ if not bypass_validation:
344
+ engine.run(model_class, VALIDATE_CREATE, [parent_obj], ctx=ctx)
345
+ engine.run(model_class, BEFORE_CREATE, [parent_obj], ctx=ctx)
346
+
345
347
  # Use Django's base manager to create the object and get PKs back
346
348
  # This bypasses hooks and the MTI exception
347
349
  field_values = {
@@ -349,32 +351,32 @@ class HookQuerySet(models.QuerySet):
349
351
  for field in model_class._meta.local_fields
350
352
  if hasattr(parent_obj, field.name) and getattr(parent_obj, field.name) is not None
351
353
  }
352
- print(f"DEBUG: Creating {model_class.__name__} with field_values: {field_values}")
353
354
  created_obj = model_class._base_manager.using(self.db).create(**field_values)
354
- print(f"DEBUG: Created {model_class.__name__} with PK: {created_obj.pk}")
355
+
355
356
  # Update the parent_obj with the created object's PK
356
357
  parent_obj.pk = created_obj.pk
357
358
  parent_obj._state.adding = False
358
359
  parent_obj._state.db = self.db
360
+
361
+ # Fire AFTER_CREATE hooks for parent
362
+ if not bypass_hooks:
363
+ engine.run(model_class, AFTER_CREATE, [parent_obj], ctx=ctx)
364
+
359
365
  parent_instances[model_class] = parent_obj
360
366
  current_parent = parent_obj
361
367
  parent_objects_map[id(obj)] = parent_instances
362
368
 
363
369
  # Step 2: Create all child objects and do single bulk insert into childmost table
364
370
  child_model = inheritance_chain[-1]
365
- print(f"DEBUG: Creating child objects for {child_model.__name__}")
366
371
  all_child_objects = []
367
372
  for obj in batch:
368
373
  child_obj = self._create_child_instance(
369
374
  obj, child_model, parent_objects_map.get(id(obj), {})
370
375
  )
371
376
  all_child_objects.append(child_obj)
372
- print(f"DEBUG: Created {len(all_child_objects)} child objects")
373
377
 
374
378
  # Step 2.5: Use Django's internal bulk_create infrastructure
375
379
  if all_child_objects:
376
- print(f"DEBUG: Using Django's internal bulk_create infrastructure for {len(all_child_objects)} child objects")
377
-
378
380
  # Get the base manager's queryset
379
381
  base_qs = child_model._base_manager.using(self.db)
380
382
 
@@ -389,38 +391,17 @@ class HookQuerySet(models.QuerySet):
389
391
  else:
390
392
  objs_without_pk.append(obj)
391
393
 
392
- print(f"DEBUG: Prepared {len(objs_with_pk)} objects with PK, {len(objs_without_pk)} objects without PK")
393
-
394
394
  # Use Django's internal _batched_insert method
395
395
  opts = child_model._meta
396
396
  # For child models in MTI, we need to include the foreign key to the parent
397
397
  # but exclude the primary key since it's inherited
398
- print(f"DEBUG: All local fields: {[f.name for f in opts.local_fields]}")
399
398
 
400
399
  # Include all local fields except generated ones
401
400
  # We need to include the foreign key to the parent (business_ptr)
402
401
  fields = [f for f in opts.local_fields if not f.generated]
403
- print(f"DEBUG: Child model fields to insert: {[f.name for f in fields]}")
404
-
405
- # Debug: Check what fields are actually set on the child objects
406
- for i, child_obj in enumerate(all_child_objects[:3]): # Check first 3 objects
407
- print(f"DEBUG: Child object {i} fields: {[f.name for f in child_model._meta.local_fields if hasattr(child_obj, f.name) and getattr(child_obj, f.name) is not None]}")
408
- # Debug: Check the actual values
409
- for field in child_model._meta.local_fields:
410
- if hasattr(child_obj, field.name) and getattr(child_obj, field.name) is not None:
411
- value = getattr(child_obj, field.name)
412
- if hasattr(value, 'pk'):
413
- print(f"DEBUG: Child object {i} {field.name} = {value} (PK: {value.pk})")
414
- else:
415
- print(f"DEBUG: Child object {i} {field.name} = {value}")
416
-
417
- # Debug: Check what fields are actually set on the child objects
418
- for i, child_obj in enumerate(all_child_objects[:3]): # Check first 3 objects
419
- print(f"DEBUG: Child object {i} fields: {[f.name for f in child_model._meta.local_fields if hasattr(child_obj, f.name) and getattr(child_obj, f.name) is not None]}")
420
402
 
421
403
  with transaction.atomic(using=self.db, savepoint=False):
422
404
  if objs_with_pk:
423
- print(f"DEBUG: Inserting {len(objs_with_pk)} objects with PK")
424
405
  returned_columns = base_qs._batched_insert(
425
406
  objs_with_pk,
426
407
  fields,
@@ -435,7 +416,6 @@ class HookQuerySet(models.QuerySet):
435
416
  obj_with_pk._state.db = self.db
436
417
 
437
418
  if objs_without_pk:
438
- print(f"DEBUG: Inserting {len(objs_without_pk)} objects without PK")
439
419
  # For objects without PK, we still need to exclude primary key fields
440
420
  fields = [f for f in fields if not isinstance(f, AutoField) and not f.primary_key]
441
421
  returned_columns = base_qs._batched_insert(
@@ -448,20 +428,15 @@ class HookQuerySet(models.QuerySet):
448
428
  setattr(obj_without_pk, field.attname, result)
449
429
  obj_without_pk._state.adding = False
450
430
  obj_without_pk._state.db = self.db
451
-
452
- print(f"DEBUG: Successfully bulk created child objects using Django's internal methods")
453
431
 
454
432
  # Step 3: Update original objects with generated PKs and state
455
433
  pk_field_name = child_model._meta.pk.name
456
- print(f"DEBUG: Updating original objects with PK field: {pk_field_name}")
457
434
  for orig_obj, child_obj in zip(batch, all_child_objects):
458
435
  child_pk = getattr(child_obj, pk_field_name)
459
- print(f"DEBUG: Setting {orig_obj.__class__.__name__} PK to {child_pk}")
460
436
  setattr(orig_obj, pk_field_name, child_pk)
461
437
  orig_obj._state.adding = False
462
438
  orig_obj._state.db = self.db
463
439
 
464
- print(f"DEBUG: Completed processing batch")
465
440
  return batch
466
441
 
467
442
  def _create_parent_instance(self, source_obj, parent_model, current_parent):
@@ -510,14 +485,10 @@ class HookQuerySet(models.QuerySet):
510
485
  for parent_model, parent_instance in parent_instances.items():
511
486
  parent_link = child_model._meta.get_ancestor_link(parent_model)
512
487
  if parent_link:
513
- print(f"DEBUG: Parent link: {parent_link.name}, target_field: {parent_link.target_field.name}, attname: {parent_link.target_field.attname}")
514
- print(f"DEBUG: Parent link attname: {parent_link.attname}")
515
488
  # Set both the foreign key value (the ID) and the object reference
516
489
  # This follows Django's pattern in _set_pk_val
517
490
  setattr(child_obj, parent_link.attname, parent_instance.pk) # Set the foreign key value
518
491
  setattr(child_obj, parent_link.name, parent_instance) # Set the object reference
519
- print(f"DEBUG: Set {parent_link.attname} to {parent_instance.pk}")
520
- print(f"DEBUG: Set {parent_link.name} to {parent_instance}")
521
492
 
522
493
  # Handle auto_now_add and auto_now fields like Django does
523
494
  for field in child_model._meta.local_fields:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-bulk-hooks"
3
- version = "0.1.146"
3
+ version = "0.1.148"
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"