django-bulk-hooks 0.1.267__tar.gz → 0.1.269__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.267 → django_bulk_hooks-0.1.269}/PKG-INFO +1 -1
  2. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/django_bulk_hooks/queryset.py +235 -112
  3. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/pyproject.toml +1 -1
  4. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/LICENSE +0 -0
  5. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/README.md +0 -0
  6. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/django_bulk_hooks/__init__.py +0 -0
  7. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/django_bulk_hooks/conditions.py +0 -0
  8. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/django_bulk_hooks/constants.py +0 -0
  9. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/django_bulk_hooks/context.py +0 -0
  10. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/django_bulk_hooks/decorators.py +0 -0
  11. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/django_bulk_hooks/engine.py +0 -0
  12. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/django_bulk_hooks/enums.py +0 -0
  13. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/django_bulk_hooks/handler.py +0 -0
  14. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/django_bulk_hooks/manager.py +0 -0
  15. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/django_bulk_hooks/models.py +0 -0
  16. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/django_bulk_hooks/priority.py +0 -0
  17. {django_bulk_hooks-0.1.267 → django_bulk_hooks-0.1.269}/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.267
3
+ Version: 0.1.269
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
@@ -485,19 +485,12 @@ class HookQuerySetMixin:
485
485
  but supports multi-table inheritance (MTI) models and hooks. All arguments are supported and
486
486
  passed through to the correct logic. For MTI, only a subset of options may be supported.
487
487
  """
488
- model_cls = self.model
489
-
490
- print(
491
- f"DEBUG: bulk_create called for {model_cls.__name__} with {len(objs)} objects"
492
- )
493
- print(
494
- f"DEBUG: update_conflicts={update_conflicts}, unique_fields={unique_fields}, update_fields={update_fields}"
495
- )
496
- logger.debug(
497
- f"bulk_create called for {model_cls.__name__} with {len(objs)} objects"
498
- )
499
- logger.debug(
500
- f"update_conflicts={update_conflicts}, unique_fields={unique_fields}, update_fields={update_fields}"
488
+ model_cls, ctx, originals = self._setup_bulk_operation(
489
+ objs, "bulk_create", require_pks=False,
490
+ bypass_hooks=bypass_hooks, bypass_validation=bypass_validation,
491
+ update_conflicts=update_conflicts,
492
+ unique_fields=unique_fields,
493
+ update_fields=update_fields
501
494
  )
502
495
 
503
496
  # When you bulk insert you don't get the primary keys back (if it's an
@@ -518,26 +511,13 @@ class HookQuerySetMixin:
518
511
  if not objs:
519
512
  return objs
520
513
 
521
- if any(not isinstance(obj, model_cls) for obj in objs):
522
- raise TypeError(
523
- f"bulk_create expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
524
- )
514
+ self._validate_objects(objs, require_pks=False, operation_name="bulk_create")
525
515
 
526
516
  # Check for MTI - if we detect multi-table inheritance, we need special handling
527
- # This follows Django's approach: check that the parents share the same concrete model
528
- # with our model to detect the inheritance pattern ConcreteGrandParent ->
529
- # MultiTableParent -> ProxyChild. Simply checking self.model._meta.proxy would not
530
- # identify that case as involving multiple tables.
531
- is_mti = False
532
- for parent in model_cls._meta.all_parents:
533
- if parent._meta.concrete_model is not model_cls._meta.concrete_model:
534
- is_mti = True
535
- break
517
+ is_mti = self._is_multi_table_inheritance()
536
518
 
537
519
  # Fire hooks before DB ops
538
520
  if not bypass_hooks:
539
- ctx = HookContext(model_cls, bypass_hooks=False) # Pass bypass_hooks
540
-
541
521
  if update_conflicts and unique_fields:
542
522
  # For upsert operations, we need to determine which records will be created vs updated
543
523
  # Check which records already exist in the database based on unique fields
@@ -555,6 +535,9 @@ class HookQuerySetMixin:
555
535
  for field_name in unique_fields:
556
536
  if hasattr(obj, field_name):
557
537
  unique_value[field_name] = getattr(obj, field_name)
538
+ elif hasattr(obj, field_name + '_id'):
539
+ # Handle ForeignKey fields where _id suffix is used
540
+ unique_value[field_name] = getattr(obj, field_name + '_id')
558
541
  if unique_value:
559
542
  unique_values.append(unique_value)
560
543
 
@@ -583,6 +566,9 @@ class HookQuerySetMixin:
583
566
  for field_name in unique_fields:
584
567
  if hasattr(obj, field_name):
585
568
  obj_unique_value[field_name] = getattr(obj, field_name)
569
+ elif hasattr(obj, field_name + '_id'):
570
+ # Handle ForeignKey fields where _id suffix is used
571
+ obj_unique_value[field_name] = getattr(obj, field_name + '_id')
586
572
 
587
573
  # Check if this record already exists using our bulk lookup
588
574
  if obj_unique_value:
@@ -604,13 +590,7 @@ class HookQuerySetMixin:
604
590
 
605
591
  # Handle auto_now fields intelligently for upsert operations
606
592
  # Only set auto_now fields on records that will actually be created
607
- for obj in new_records:
608
- for field in model_cls._meta.local_fields:
609
- if hasattr(field, "auto_now") and field.auto_now:
610
- field.pre_save(obj, add=True)
611
- elif hasattr(field, "auto_now_add") and field.auto_now_add:
612
- if getattr(obj, field.name) is None:
613
- field.pre_save(obj, add=True)
593
+ self._handle_auto_now_fields(new_records, add=True)
614
594
 
615
595
  # For existing records, preserve their original auto_now values
616
596
  # We'll need to fetch them from the database to preserve the timestamps
@@ -737,19 +717,12 @@ class HookQuerySetMixin:
737
717
  else:
738
718
  # For regular create operations, run create hooks before DB ops
739
719
  # Handle auto_now fields normally for new records
740
- for obj in objs:
741
- for field in model_cls._meta.local_fields:
742
- if hasattr(field, "auto_now") and field.auto_now:
743
- field.pre_save(obj, add=True)
744
- elif hasattr(field, "auto_now_add") and field.auto_now_add:
745
- if getattr(obj, field.name) is None:
746
- field.pre_save(obj, add=True)
720
+ self._handle_auto_now_fields(objs, add=True)
747
721
 
748
722
  if not bypass_validation:
749
723
  engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
750
724
  engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
751
725
  else:
752
- ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
753
726
  logger.debug("bulk_create bypassed hooks")
754
727
 
755
728
  # For MTI models, we need to handle them specially
@@ -978,11 +951,11 @@ class HookQuerySetMixin:
978
951
  if not objs:
979
952
  return []
980
953
 
981
- self._validate_objects(objs)
954
+ self._validate_objects(objs, require_pks=True, operation_name="bulk_update")
982
955
 
983
956
  changed_fields = self._detect_changed_fields(objs)
984
957
  is_mti = self._is_multi_table_inheritance()
985
- hook_context, originals = self._init_hook_context(bypass_hooks, objs)
958
+ hook_context, originals = self._init_hook_context(bypass_hooks, objs, "bulk_update")
986
959
 
987
960
  fields_set, auto_now_fields, custom_update_fields = self._prepare_update_fields(
988
961
  changed_fields
@@ -1128,10 +1101,14 @@ class HookQuerySetMixin:
1128
1101
  logger.debug("Built value_map for %d objects", len(value_map))
1129
1102
  return value_map
1130
1103
 
1131
- def _validate_objects(self, objs):
1104
+ def _validate_objects(self, objs, require_pks=False, operation_name="bulk_update"):
1132
1105
  """
1133
- Validate that all objects are instances of this queryset's model
1134
- and that they have primary keys (cannot bulk update unsaved objects).
1106
+ Validate that all objects are instances of this queryset's model.
1107
+
1108
+ Args:
1109
+ objs (list): Objects to validate
1110
+ require_pks (bool): Whether to validate that objects have primary keys
1111
+ operation_name (str): Name of the operation for error messages
1135
1112
  """
1136
1113
  model_cls = self.model
1137
1114
 
@@ -1141,27 +1118,34 @@ class HookQuerySetMixin:
1141
1118
  }
1142
1119
  if invalid_types:
1143
1120
  raise TypeError(
1144
- f"bulk_update expected instances of {model_cls.__name__}, "
1121
+ f"{operation_name} expected instances of {model_cls.__name__}, "
1145
1122
  f"but got {invalid_types}"
1146
1123
  )
1147
1124
 
1148
- # Primary key check
1149
- missing_pks = [obj for obj in objs if obj.pk is None]
1150
- if missing_pks:
1151
- raise ValueError(
1152
- f"bulk_update cannot operate on unsaved {model_cls.__name__} instances. "
1153
- f"{len(missing_pks)} object(s) have no primary key."
1154
- )
1125
+ # Primary key check (optional, for operations that require saved objects)
1126
+ if require_pks:
1127
+ missing_pks = [obj for obj in objs if obj.pk is None]
1128
+ if missing_pks:
1129
+ raise ValueError(
1130
+ f"{operation_name} cannot operate on unsaved {model_cls.__name__} instances. "
1131
+ f"{len(missing_pks)} object(s) have no primary key."
1132
+ )
1155
1133
 
1156
1134
  logger.debug(
1157
- "Validated %d %s objects for bulk_update",
1135
+ "Validated %d %s objects for %s",
1158
1136
  len(objs),
1159
1137
  model_cls.__name__,
1138
+ operation_name,
1160
1139
  )
1161
1140
 
1162
- def _init_hook_context(self, bypass_hooks: bool, objs):
1141
+ def _init_hook_context(self, bypass_hooks: bool, objs, operation_name="bulk_update"):
1163
1142
  """
1164
- Initialize the hook context for bulk_update.
1143
+ Initialize the hook context for bulk operations.
1144
+
1145
+ Args:
1146
+ bypass_hooks (bool): Whether to bypass hooks
1147
+ objs (list): List of objects being operated on
1148
+ operation_name (str): Name of the operation for logging
1165
1149
 
1166
1150
  Returns:
1167
1151
  (HookContext, list): The hook context and a placeholder list
@@ -1171,10 +1155,10 @@ class HookQuerySetMixin:
1171
1155
  model_cls = self.model
1172
1156
 
1173
1157
  if bypass_hooks:
1174
- logger.debug("bulk_update: hooks bypassed for %s", model_cls.__name__)
1158
+ logger.debug("%s: hooks bypassed for %s", operation_name, model_cls.__name__)
1175
1159
  ctx = HookContext(model_cls, bypass_hooks=True)
1176
1160
  else:
1177
- logger.debug("bulk_update: hooks enabled for %s", model_cls.__name__)
1161
+ logger.debug("%s: hooks enabled for %s", operation_name, model_cls.__name__)
1178
1162
  ctx = HookContext(model_cls, bypass_hooks=False)
1179
1163
 
1180
1164
  # Keep `originals` aligned with objs to support later hook execution.
@@ -1234,13 +1218,14 @@ class HookQuerySetMixin:
1234
1218
 
1235
1219
  return fields_set, auto_now_fields, custom_update_fields
1236
1220
 
1237
- def _apply_auto_now_fields(self, objs, auto_now_fields):
1221
+ def _apply_auto_now_fields(self, objs, auto_now_fields, add=False):
1238
1222
  """
1239
1223
  Apply the current timestamp to all auto_now fields on each object.
1240
1224
 
1241
1225
  Args:
1242
- objs (list[Model]): The model instances being updated.
1226
+ objs (list[Model]): The model instances being processed.
1243
1227
  auto_now_fields (list[str]): Field names that require auto_now behavior.
1228
+ add (bool): Whether this is for creation (add=True) or update (add=False).
1244
1229
  """
1245
1230
  if not auto_now_fields:
1246
1231
  return
@@ -1250,16 +1235,186 @@ class HookQuerySetMixin:
1250
1235
  current_time = timezone.now()
1251
1236
 
1252
1237
  logger.debug(
1253
- "Setting auto_now fields %s to %s for %d objects",
1238
+ "Setting auto_now fields %s to %s for %d objects (add=%s)",
1254
1239
  auto_now_fields,
1255
1240
  current_time,
1256
1241
  len(objs),
1242
+ add,
1257
1243
  )
1258
1244
 
1259
1245
  for obj in objs:
1260
1246
  for field_name in auto_now_fields:
1261
1247
  setattr(obj, field_name, current_time)
1262
1248
 
1249
+ def _handle_auto_now_fields(self, objs, add=False):
1250
+ """
1251
+ Handle auto_now and auto_now_add fields for objects.
1252
+
1253
+ Args:
1254
+ objs (list[Model]): The model instances being processed.
1255
+ add (bool): Whether this is for creation (add=True) or update (add=False).
1256
+
1257
+ Returns:
1258
+ list[str]: Names of auto_now fields that were handled.
1259
+ """
1260
+ model_cls = self.model
1261
+ handled_fields = []
1262
+
1263
+ for obj in objs:
1264
+ for field in model_cls._meta.local_fields:
1265
+ # Handle auto_now_add only during creation
1266
+ if add and hasattr(field, "auto_now_add") and field.auto_now_add:
1267
+ if getattr(obj, field.name) is None:
1268
+ field.pre_save(obj, add=True)
1269
+ handled_fields.append(field.name)
1270
+ # Handle auto_now during creation or update
1271
+ elif hasattr(field, "auto_now") and field.auto_now:
1272
+ field.pre_save(obj, add=add)
1273
+ handled_fields.append(field.name)
1274
+
1275
+ return list(set(handled_fields)) # Remove duplicates
1276
+
1277
+ def _execute_hooks_with_operation(self, operation_func, validate_hook, before_hook, after_hook, objs, originals=None, ctx=None, bypass_hooks=False, bypass_validation=False):
1278
+ """
1279
+ Execute the complete hook lifecycle around a database operation.
1280
+
1281
+ Args:
1282
+ operation_func (callable): The database operation to execute
1283
+ validate_hook: Hook constant for validation
1284
+ before_hook: Hook constant for before operation
1285
+ after_hook: Hook constant for after operation
1286
+ objs (list): Objects being operated on
1287
+ originals (list, optional): Original objects for comparison hooks
1288
+ ctx: Hook context
1289
+ bypass_hooks (bool): Whether to skip hooks
1290
+ bypass_validation (bool): Whether to skip validation hooks
1291
+
1292
+ Returns:
1293
+ The result of the database operation
1294
+ """
1295
+ model_cls = self.model
1296
+
1297
+ # Run validation hooks first (if not bypassed)
1298
+ if not bypass_validation and validate_hook:
1299
+ engine.run(model_cls, validate_hook, objs, ctx=ctx)
1300
+
1301
+ # Run before hooks (if not bypassed)
1302
+ if not bypass_hooks and before_hook:
1303
+ engine.run(model_cls, before_hook, objs, originals, ctx=ctx)
1304
+
1305
+ # Execute the database operation
1306
+ result = operation_func()
1307
+
1308
+ # Run after hooks (if not bypassed)
1309
+ if not bypass_hooks and after_hook:
1310
+ engine.run(model_cls, after_hook, objs, originals, ctx=ctx)
1311
+
1312
+ return result
1313
+
1314
+ def _log_bulk_operation_start(self, operation_name, objs, **kwargs):
1315
+ """
1316
+ Log the start of a bulk operation with consistent formatting.
1317
+
1318
+ Args:
1319
+ operation_name (str): Name of the operation (e.g., "bulk_create")
1320
+ objs (list): Objects being operated on
1321
+ **kwargs: Additional parameters to log
1322
+ """
1323
+ model_cls = self.model
1324
+
1325
+ # Build parameter string for additional kwargs
1326
+ param_str = ""
1327
+ if kwargs:
1328
+ param_parts = []
1329
+ for key, value in kwargs.items():
1330
+ if isinstance(value, (list, tuple)):
1331
+ param_parts.append(f"{key}={value}")
1332
+ else:
1333
+ param_parts.append(f"{key}={value}")
1334
+ param_str = f", {', '.join(param_parts)}"
1335
+
1336
+ # Use both print and logger for consistency with existing patterns
1337
+ print(f"DEBUG: {operation_name} called for {model_cls.__name__} with {len(objs)} objects{param_str}")
1338
+ logger.debug(f"{operation_name} called for {model_cls.__name__} with {len(objs)} objects{param_str}")
1339
+
1340
+ def _execute_delete_hooks_with_operation(self, operation_func, objs, ctx=None, bypass_hooks=False, bypass_validation=False):
1341
+ """
1342
+ Execute hooks for delete operations with special field caching logic.
1343
+
1344
+ Args:
1345
+ operation_func (callable): The delete operation to execute
1346
+ objs (list): Objects being deleted
1347
+ ctx: Hook context
1348
+ bypass_hooks (bool): Whether to skip hooks
1349
+ bypass_validation (bool): Whether to skip validation hooks
1350
+
1351
+ Returns:
1352
+ The result of the delete operation
1353
+ """
1354
+ model_cls = self.model
1355
+
1356
+ # Run validation hooks first (if not bypassed)
1357
+ if not bypass_validation:
1358
+ engine.run(model_cls, VALIDATE_DELETE, objs, ctx=ctx)
1359
+
1360
+ # Run before hooks (if not bypassed)
1361
+ if not bypass_hooks:
1362
+ engine.run(model_cls, BEFORE_DELETE, objs, ctx=ctx)
1363
+
1364
+ # Before deletion, ensure all related fields are properly cached
1365
+ # to avoid DoesNotExist errors in AFTER_DELETE hooks
1366
+ for obj in objs:
1367
+ if obj.pk is not None:
1368
+ # Cache all foreign key relationships by accessing them
1369
+ for field in model_cls._meta.fields:
1370
+ if (
1371
+ field.is_relation
1372
+ and not field.many_to_many
1373
+ and not field.one_to_many
1374
+ ):
1375
+ try:
1376
+ # Access the related field to cache it before deletion
1377
+ getattr(obj, field.name)
1378
+ except Exception:
1379
+ # If we can't access the field (e.g., already deleted, no permission, etc.)
1380
+ # continue with other fields
1381
+ pass
1382
+
1383
+ # Execute the database operation
1384
+ result = operation_func()
1385
+
1386
+ # Run after hooks (if not bypassed)
1387
+ if not bypass_hooks:
1388
+ engine.run(model_cls, AFTER_DELETE, objs, ctx=ctx)
1389
+
1390
+ return result
1391
+
1392
+ def _setup_bulk_operation(self, objs, operation_name, require_pks=False, bypass_hooks=False, bypass_validation=False, **log_kwargs):
1393
+ """
1394
+ Common setup logic for bulk operations.
1395
+
1396
+ Args:
1397
+ objs (list): Objects to operate on
1398
+ operation_name (str): Name of the operation for logging and validation
1399
+ require_pks (bool): Whether objects must have primary keys
1400
+ bypass_hooks (bool): Whether to bypass hooks
1401
+ bypass_validation (bool): Whether to bypass validation
1402
+ **log_kwargs: Additional parameters to log
1403
+
1404
+ Returns:
1405
+ tuple: (model_cls, ctx, originals)
1406
+ """
1407
+ # Log operation start
1408
+ self._log_bulk_operation_start(operation_name, objs, **log_kwargs)
1409
+
1410
+ # Validate objects
1411
+ self._validate_objects(objs, require_pks=require_pks, operation_name=operation_name)
1412
+
1413
+ # Initialize hook context
1414
+ ctx, originals = self._init_hook_context(bypass_hooks, objs, operation_name)
1415
+
1416
+ return self.model, ctx, originals
1417
+
1263
1418
  def _is_multi_table_inheritance(self) -> bool:
1264
1419
  """
1265
1420
  Determine whether this model uses multi-table inheritance (MTI).
@@ -1271,7 +1426,7 @@ class HookQuerySetMixin:
1271
1426
  logger.debug(
1272
1427
  "%s detected as MTI model (parent: %s)",
1273
1428
  model_cls.__name__,
1274
- parent.__name__,
1429
+ getattr(parent, "__name__", str(parent)),
1275
1430
  )
1276
1431
  return True
1277
1432
 
@@ -1793,56 +1948,24 @@ class HookQuerySetMixin:
1793
1948
  if not objs:
1794
1949
  return 0
1795
1950
 
1796
- if any(not isinstance(obj, model_cls) for obj in objs):
1797
- raise TypeError(
1798
- f"bulk_delete expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
1799
- )
1800
-
1801
- logger.debug(
1802
- f"bulk_delete {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}"
1951
+ model_cls, ctx, _ = self._setup_bulk_operation(
1952
+ objs, "bulk_delete", require_pks=True,
1953
+ bypass_hooks=bypass_hooks, bypass_validation=bypass_validation
1803
1954
  )
1804
1955
 
1805
- # Fire hooks before DB ops
1806
- if not bypass_hooks:
1807
- ctx = HookContext(model_cls, bypass_hooks=False)
1808
- if not bypass_validation:
1809
- engine.run(model_cls, VALIDATE_DELETE, objs, ctx=ctx)
1810
- engine.run(model_cls, BEFORE_DELETE, objs, ctx=ctx)
1811
- else:
1812
- ctx = HookContext(model_cls, bypass_hooks=True)
1813
- logger.debug("bulk_delete bypassed hooks")
1814
-
1815
- # Before deletion, ensure all related fields are properly cached
1816
- # to avoid DoesNotExist errors in AFTER_DELETE hooks
1817
- if not bypass_hooks:
1818
- for obj in objs:
1819
- if obj.pk is not None:
1820
- # Cache all foreign key relationships by accessing them
1821
- for field in model_cls._meta.fields:
1822
- if (
1823
- field.is_relation
1824
- and not field.many_to_many
1825
- and not field.one_to_many
1826
- ):
1827
- try:
1828
- # Access the related field to cache it before deletion
1829
- getattr(obj, field.name)
1830
- except Exception:
1831
- # If we can't access the field (e.g., already deleted, no permission, etc.)
1832
- # continue with other fields
1833
- pass
1834
-
1835
- # Use Django's standard delete() method on the queryset
1836
- pks = [obj.pk for obj in objs if obj.pk is not None]
1837
- if pks:
1838
- # Use the base manager to avoid recursion
1839
- result = self.model._base_manager.filter(pk__in=pks).delete()[0]
1840
- else:
1841
- result = 0
1956
+ # Execute the database operation with hooks
1957
+ def delete_operation():
1958
+ pks = [obj.pk for obj in objs if obj.pk is not None]
1959
+ if pks:
1960
+ # Use the base manager to avoid recursion
1961
+ return self.model._base_manager.filter(pk__in=pks).delete()[0]
1962
+ else:
1963
+ return 0
1842
1964
 
1843
- # Fire AFTER_DELETE hooks
1844
- if not bypass_hooks:
1845
- engine.run(model_cls, AFTER_DELETE, objs, ctx=ctx)
1965
+ result = self._execute_delete_hooks_with_operation(
1966
+ delete_operation, objs, ctx=ctx,
1967
+ bypass_hooks=bypass_hooks, bypass_validation=bypass_validation
1968
+ )
1846
1969
 
1847
1970
  return result
1848
1971
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-bulk-hooks"
3
- version = "0.1.267"
3
+ version = "0.1.269"
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"