django-bulk-hooks 0.1.123__tar.gz → 0.1.125__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.123 → django_bulk_hooks-0.1.125}/PKG-INFO +1 -1
  2. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/django_bulk_hooks/queryset.py +32 -41
  3. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/pyproject.toml +1 -1
  4. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/LICENSE +0 -0
  5. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/README.md +0 -0
  6. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/django_bulk_hooks/__init__.py +0 -0
  7. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/django_bulk_hooks/conditions.py +0 -0
  8. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/django_bulk_hooks/constants.py +0 -0
  9. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/django_bulk_hooks/context.py +0 -0
  10. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/django_bulk_hooks/decorators.py +0 -0
  11. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/django_bulk_hooks/engine.py +0 -0
  12. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/django_bulk_hooks/enums.py +0 -0
  13. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/django_bulk_hooks/handler.py +0 -0
  14. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/django_bulk_hooks/manager.py +0 -0
  15. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/django_bulk_hooks/models.py +0 -0
  16. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/django_bulk_hooks/priority.py +0 -0
  17. {django_bulk_hooks-0.1.123 → django_bulk_hooks-0.1.125}/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.123
3
+ Version: 0.1.125
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
@@ -1,10 +1,6 @@
1
- from django.db import models, transaction, connections
2
- from django.db.models import AutoField, Q, Max
3
- from django.db import NotSupportedError
4
- from django.db.models.constants import OnConflict
5
- from django.db.models.expressions import DatabaseDefault
6
- import operator
7
- from functools import reduce
1
+
2
+ from django.db import models, transaction
3
+ from django.db.models import AutoField
8
4
 
9
5
  from django_bulk_hooks import engine
10
6
  from django_bulk_hooks.constants import (
@@ -302,9 +298,13 @@ class HookQuerySet(models.QuerySet):
302
298
  def _process_mti_batch(self, batch, inheritance_chain, **kwargs):
303
299
  """
304
300
  Process a single batch of objects through the inheritance chain.
301
+ Reuses Django's internal functions as much as possible.
305
302
  """
306
- # Step 1: Handle parent tables with individual saves (needed for PKs)
303
+ # For MTI, we need to save parent objects first to get PKs
304
+ # Then we can use Django's bulk_create for the child objects
307
305
  parent_objects_map = {}
306
+
307
+ # Step 1: Save parent objects (we need their PKs for child objects)
308
308
  for obj in batch:
309
309
  parent_instances = {}
310
310
  current_parent = None
@@ -312,11 +312,13 @@ class HookQuerySet(models.QuerySet):
312
312
  parent_obj = self._create_parent_instance(
313
313
  obj, model_class, current_parent
314
314
  )
315
- parent_obj.save()
315
+ # Use Django's internal save method to avoid hooks
316
+ models.Model.save(parent_obj)
316
317
  parent_instances[model_class] = parent_obj
317
318
  current_parent = parent_obj
318
319
  parent_objects_map[id(obj)] = parent_instances
319
- # Step 2: Bulk insert for child objects
320
+
321
+ # Step 2: Create and bulk insert child objects
320
322
  child_model = inheritance_chain[-1]
321
323
  child_objects = []
322
324
  for obj in batch:
@@ -324,43 +326,19 @@ class HookQuerySet(models.QuerySet):
324
326
  obj, child_model, parent_objects_map.get(id(obj), {})
325
327
  )
326
328
  child_objects.append(child_obj)
327
- # If the child model is still MTI, call our own logic recursively
328
- if len([p for p in child_model._meta.parents.keys() if not p._meta.proxy]) > 0:
329
- # Build inheritance chain for the child model
330
- child_inheritance_chain = []
331
- current_model = child_model
332
- while current_model:
333
- if not current_model._meta.proxy:
334
- child_inheritance_chain.append(current_model)
335
- parents = [
336
- parent
337
- for parent in current_model._meta.parents.keys()
338
- if not parent._meta.proxy
339
- ]
340
- current_model = parents[0] if parents else None
341
- child_inheritance_chain.reverse()
342
-
343
- # For nested MTI, we can't use bulk operations recursively
344
- # because it would create infinite recursion. Instead, we save each child individually.
345
- # We use super().save() to avoid triggering hooks that would cause recursion.
346
- created = []
347
- for child_obj in child_objects:
348
- # Use the base model's save method to avoid triggering hooks
349
- # This prevents infinite recursion when hooks try to query the database
350
- super(child_obj.__class__, child_obj).save()
351
- created.append(child_obj)
352
- else:
353
- # Single-table, safe to use bulk_create
354
-
355
- child_manager = child_model._base_manager
356
- child_manager._for_write = True
357
- created = child_manager.bulk_create(child_objects, **kwargs)
329
+
330
+ # Use Django's bulk_create for child objects - this handles auto_now_add correctly
331
+ child_manager = child_model._base_manager
332
+ child_manager._for_write = True
333
+ created = child_manager.bulk_create(child_objects, **kwargs)
334
+
358
335
  # Step 3: Update original objects with generated PKs and state
359
336
  pk_field_name = child_model._meta.pk.name
360
337
  for orig_obj, child_obj in zip(batch, created):
361
338
  setattr(orig_obj, pk_field_name, getattr(child_obj, pk_field_name))
362
339
  orig_obj._state.adding = False
363
340
  orig_obj._state.db = self.db
341
+
364
342
  return batch
365
343
 
366
344
  def _create_parent_instance(self, source_obj, parent_model, current_parent):
@@ -388,6 +366,12 @@ class HookQuerySet(models.QuerySet):
388
366
  elif hasattr(field, 'auto_now') and field.auto_now:
389
367
  field.pre_save(parent_obj, add=True)
390
368
 
369
+ # Ensure auto_now_add fields are explicitly set to prevent null constraint violations
370
+ for field in parent_model._meta.local_fields:
371
+ if hasattr(field, 'auto_now_add') and field.auto_now_add:
372
+ if getattr(parent_obj, field.name) is None:
373
+ field.pre_save(parent_obj, add=True)
374
+
391
375
  return parent_obj
392
376
 
393
377
  def _create_child_instance(self, source_obj, child_model, parent_instances):
@@ -411,4 +395,11 @@ class HookQuerySet(models.QuerySet):
411
395
  elif hasattr(field, 'auto_now') and field.auto_now:
412
396
  field.pre_save(child_obj, add=True)
413
397
 
398
+ # Ensure auto_now_add fields are explicitly set to prevent null constraint violations
399
+ for field in child_model._meta.local_fields:
400
+ if hasattr(field, 'auto_now_add') and field.auto_now_add:
401
+ if getattr(child_obj, field.name) is None:
402
+ field.pre_save(child_obj, add=True)
403
+
414
404
  return child_obj
405
+
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-bulk-hooks"
3
- version = "0.1.123"
3
+ version = "0.1.125"
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"