django-bulk-hooks 0.1.267__py3-none-any.whl → 0.1.268__py3-none-any.whl
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.
- django_bulk_hooks/queryset.py +229 -112
- {django_bulk_hooks-0.1.267.dist-info → django_bulk_hooks-0.1.268.dist-info}/METADATA +1 -1
- {django_bulk_hooks-0.1.267.dist-info → django_bulk_hooks-0.1.268.dist-info}/RECORD +5 -5
- {django_bulk_hooks-0.1.267.dist-info → django_bulk_hooks-0.1.268.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.1.267.dist-info → django_bulk_hooks-0.1.268.dist-info}/WHEEL +0 -0
django_bulk_hooks/queryset.py
CHANGED
|
@@ -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.
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -604,13 +584,7 @@ class HookQuerySetMixin:
|
|
|
604
584
|
|
|
605
585
|
# Handle auto_now fields intelligently for upsert operations
|
|
606
586
|
# Only set auto_now fields on records that will actually be created
|
|
607
|
-
|
|
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)
|
|
587
|
+
self._handle_auto_now_fields(new_records, add=True)
|
|
614
588
|
|
|
615
589
|
# For existing records, preserve their original auto_now values
|
|
616
590
|
# We'll need to fetch them from the database to preserve the timestamps
|
|
@@ -737,19 +711,12 @@ class HookQuerySetMixin:
|
|
|
737
711
|
else:
|
|
738
712
|
# For regular create operations, run create hooks before DB ops
|
|
739
713
|
# Handle auto_now fields normally for new records
|
|
740
|
-
|
|
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)
|
|
714
|
+
self._handle_auto_now_fields(objs, add=True)
|
|
747
715
|
|
|
748
716
|
if not bypass_validation:
|
|
749
717
|
engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
|
|
750
718
|
engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
|
|
751
719
|
else:
|
|
752
|
-
ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
|
|
753
720
|
logger.debug("bulk_create bypassed hooks")
|
|
754
721
|
|
|
755
722
|
# For MTI models, we need to handle them specially
|
|
@@ -978,11 +945,11 @@ class HookQuerySetMixin:
|
|
|
978
945
|
if not objs:
|
|
979
946
|
return []
|
|
980
947
|
|
|
981
|
-
self._validate_objects(objs)
|
|
948
|
+
self._validate_objects(objs, require_pks=True, operation_name="bulk_update")
|
|
982
949
|
|
|
983
950
|
changed_fields = self._detect_changed_fields(objs)
|
|
984
951
|
is_mti = self._is_multi_table_inheritance()
|
|
985
|
-
hook_context, originals = self._init_hook_context(bypass_hooks, objs)
|
|
952
|
+
hook_context, originals = self._init_hook_context(bypass_hooks, objs, "bulk_update")
|
|
986
953
|
|
|
987
954
|
fields_set, auto_now_fields, custom_update_fields = self._prepare_update_fields(
|
|
988
955
|
changed_fields
|
|
@@ -1128,10 +1095,14 @@ class HookQuerySetMixin:
|
|
|
1128
1095
|
logger.debug("Built value_map for %d objects", len(value_map))
|
|
1129
1096
|
return value_map
|
|
1130
1097
|
|
|
1131
|
-
def _validate_objects(self, objs):
|
|
1098
|
+
def _validate_objects(self, objs, require_pks=False, operation_name="bulk_update"):
|
|
1132
1099
|
"""
|
|
1133
|
-
Validate that all objects are instances of this queryset's model
|
|
1134
|
-
|
|
1100
|
+
Validate that all objects are instances of this queryset's model.
|
|
1101
|
+
|
|
1102
|
+
Args:
|
|
1103
|
+
objs (list): Objects to validate
|
|
1104
|
+
require_pks (bool): Whether to validate that objects have primary keys
|
|
1105
|
+
operation_name (str): Name of the operation for error messages
|
|
1135
1106
|
"""
|
|
1136
1107
|
model_cls = self.model
|
|
1137
1108
|
|
|
@@ -1141,27 +1112,34 @@ class HookQuerySetMixin:
|
|
|
1141
1112
|
}
|
|
1142
1113
|
if invalid_types:
|
|
1143
1114
|
raise TypeError(
|
|
1144
|
-
f"
|
|
1115
|
+
f"{operation_name} expected instances of {model_cls.__name__}, "
|
|
1145
1116
|
f"but got {invalid_types}"
|
|
1146
1117
|
)
|
|
1147
1118
|
|
|
1148
|
-
# Primary key check
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1119
|
+
# Primary key check (optional, for operations that require saved objects)
|
|
1120
|
+
if require_pks:
|
|
1121
|
+
missing_pks = [obj for obj in objs if obj.pk is None]
|
|
1122
|
+
if missing_pks:
|
|
1123
|
+
raise ValueError(
|
|
1124
|
+
f"{operation_name} cannot operate on unsaved {model_cls.__name__} instances. "
|
|
1125
|
+
f"{len(missing_pks)} object(s) have no primary key."
|
|
1126
|
+
)
|
|
1155
1127
|
|
|
1156
1128
|
logger.debug(
|
|
1157
|
-
"Validated %d %s objects for
|
|
1129
|
+
"Validated %d %s objects for %s",
|
|
1158
1130
|
len(objs),
|
|
1159
1131
|
model_cls.__name__,
|
|
1132
|
+
operation_name,
|
|
1160
1133
|
)
|
|
1161
1134
|
|
|
1162
|
-
def _init_hook_context(self, bypass_hooks: bool, objs):
|
|
1135
|
+
def _init_hook_context(self, bypass_hooks: bool, objs, operation_name="bulk_update"):
|
|
1163
1136
|
"""
|
|
1164
|
-
Initialize the hook context for
|
|
1137
|
+
Initialize the hook context for bulk operations.
|
|
1138
|
+
|
|
1139
|
+
Args:
|
|
1140
|
+
bypass_hooks (bool): Whether to bypass hooks
|
|
1141
|
+
objs (list): List of objects being operated on
|
|
1142
|
+
operation_name (str): Name of the operation for logging
|
|
1165
1143
|
|
|
1166
1144
|
Returns:
|
|
1167
1145
|
(HookContext, list): The hook context and a placeholder list
|
|
@@ -1171,10 +1149,10 @@ class HookQuerySetMixin:
|
|
|
1171
1149
|
model_cls = self.model
|
|
1172
1150
|
|
|
1173
1151
|
if bypass_hooks:
|
|
1174
|
-
logger.debug("
|
|
1152
|
+
logger.debug("%s: hooks bypassed for %s", operation_name, model_cls.__name__)
|
|
1175
1153
|
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
1176
1154
|
else:
|
|
1177
|
-
logger.debug("
|
|
1155
|
+
logger.debug("%s: hooks enabled for %s", operation_name, model_cls.__name__)
|
|
1178
1156
|
ctx = HookContext(model_cls, bypass_hooks=False)
|
|
1179
1157
|
|
|
1180
1158
|
# Keep `originals` aligned with objs to support later hook execution.
|
|
@@ -1234,13 +1212,14 @@ class HookQuerySetMixin:
|
|
|
1234
1212
|
|
|
1235
1213
|
return fields_set, auto_now_fields, custom_update_fields
|
|
1236
1214
|
|
|
1237
|
-
def _apply_auto_now_fields(self, objs, auto_now_fields):
|
|
1215
|
+
def _apply_auto_now_fields(self, objs, auto_now_fields, add=False):
|
|
1238
1216
|
"""
|
|
1239
1217
|
Apply the current timestamp to all auto_now fields on each object.
|
|
1240
1218
|
|
|
1241
1219
|
Args:
|
|
1242
|
-
objs (list[Model]): The model instances being
|
|
1220
|
+
objs (list[Model]): The model instances being processed.
|
|
1243
1221
|
auto_now_fields (list[str]): Field names that require auto_now behavior.
|
|
1222
|
+
add (bool): Whether this is for creation (add=True) or update (add=False).
|
|
1244
1223
|
"""
|
|
1245
1224
|
if not auto_now_fields:
|
|
1246
1225
|
return
|
|
@@ -1250,16 +1229,186 @@ class HookQuerySetMixin:
|
|
|
1250
1229
|
current_time = timezone.now()
|
|
1251
1230
|
|
|
1252
1231
|
logger.debug(
|
|
1253
|
-
"Setting auto_now fields %s to %s for %d objects",
|
|
1232
|
+
"Setting auto_now fields %s to %s for %d objects (add=%s)",
|
|
1254
1233
|
auto_now_fields,
|
|
1255
1234
|
current_time,
|
|
1256
1235
|
len(objs),
|
|
1236
|
+
add,
|
|
1257
1237
|
)
|
|
1258
1238
|
|
|
1259
1239
|
for obj in objs:
|
|
1260
1240
|
for field_name in auto_now_fields:
|
|
1261
1241
|
setattr(obj, field_name, current_time)
|
|
1262
1242
|
|
|
1243
|
+
def _handle_auto_now_fields(self, objs, add=False):
|
|
1244
|
+
"""
|
|
1245
|
+
Handle auto_now and auto_now_add fields for objects.
|
|
1246
|
+
|
|
1247
|
+
Args:
|
|
1248
|
+
objs (list[Model]): The model instances being processed.
|
|
1249
|
+
add (bool): Whether this is for creation (add=True) or update (add=False).
|
|
1250
|
+
|
|
1251
|
+
Returns:
|
|
1252
|
+
list[str]: Names of auto_now fields that were handled.
|
|
1253
|
+
"""
|
|
1254
|
+
model_cls = self.model
|
|
1255
|
+
handled_fields = []
|
|
1256
|
+
|
|
1257
|
+
for obj in objs:
|
|
1258
|
+
for field in model_cls._meta.local_fields:
|
|
1259
|
+
# Handle auto_now_add only during creation
|
|
1260
|
+
if add and hasattr(field, "auto_now_add") and field.auto_now_add:
|
|
1261
|
+
if getattr(obj, field.name) is None:
|
|
1262
|
+
field.pre_save(obj, add=True)
|
|
1263
|
+
handled_fields.append(field.name)
|
|
1264
|
+
# Handle auto_now during creation or update
|
|
1265
|
+
elif hasattr(field, "auto_now") and field.auto_now:
|
|
1266
|
+
field.pre_save(obj, add=add)
|
|
1267
|
+
handled_fields.append(field.name)
|
|
1268
|
+
|
|
1269
|
+
return list(set(handled_fields)) # Remove duplicates
|
|
1270
|
+
|
|
1271
|
+
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):
|
|
1272
|
+
"""
|
|
1273
|
+
Execute the complete hook lifecycle around a database operation.
|
|
1274
|
+
|
|
1275
|
+
Args:
|
|
1276
|
+
operation_func (callable): The database operation to execute
|
|
1277
|
+
validate_hook: Hook constant for validation
|
|
1278
|
+
before_hook: Hook constant for before operation
|
|
1279
|
+
after_hook: Hook constant for after operation
|
|
1280
|
+
objs (list): Objects being operated on
|
|
1281
|
+
originals (list, optional): Original objects for comparison hooks
|
|
1282
|
+
ctx: Hook context
|
|
1283
|
+
bypass_hooks (bool): Whether to skip hooks
|
|
1284
|
+
bypass_validation (bool): Whether to skip validation hooks
|
|
1285
|
+
|
|
1286
|
+
Returns:
|
|
1287
|
+
The result of the database operation
|
|
1288
|
+
"""
|
|
1289
|
+
model_cls = self.model
|
|
1290
|
+
|
|
1291
|
+
# Run validation hooks first (if not bypassed)
|
|
1292
|
+
if not bypass_validation and validate_hook:
|
|
1293
|
+
engine.run(model_cls, validate_hook, objs, ctx=ctx)
|
|
1294
|
+
|
|
1295
|
+
# Run before hooks (if not bypassed)
|
|
1296
|
+
if not bypass_hooks and before_hook:
|
|
1297
|
+
engine.run(model_cls, before_hook, objs, originals, ctx=ctx)
|
|
1298
|
+
|
|
1299
|
+
# Execute the database operation
|
|
1300
|
+
result = operation_func()
|
|
1301
|
+
|
|
1302
|
+
# Run after hooks (if not bypassed)
|
|
1303
|
+
if not bypass_hooks and after_hook:
|
|
1304
|
+
engine.run(model_cls, after_hook, objs, originals, ctx=ctx)
|
|
1305
|
+
|
|
1306
|
+
return result
|
|
1307
|
+
|
|
1308
|
+
def _log_bulk_operation_start(self, operation_name, objs, **kwargs):
|
|
1309
|
+
"""
|
|
1310
|
+
Log the start of a bulk operation with consistent formatting.
|
|
1311
|
+
|
|
1312
|
+
Args:
|
|
1313
|
+
operation_name (str): Name of the operation (e.g., "bulk_create")
|
|
1314
|
+
objs (list): Objects being operated on
|
|
1315
|
+
**kwargs: Additional parameters to log
|
|
1316
|
+
"""
|
|
1317
|
+
model_cls = self.model
|
|
1318
|
+
|
|
1319
|
+
# Build parameter string for additional kwargs
|
|
1320
|
+
param_str = ""
|
|
1321
|
+
if kwargs:
|
|
1322
|
+
param_parts = []
|
|
1323
|
+
for key, value in kwargs.items():
|
|
1324
|
+
if isinstance(value, (list, tuple)):
|
|
1325
|
+
param_parts.append(f"{key}={value}")
|
|
1326
|
+
else:
|
|
1327
|
+
param_parts.append(f"{key}={value}")
|
|
1328
|
+
param_str = f", {', '.join(param_parts)}"
|
|
1329
|
+
|
|
1330
|
+
# Use both print and logger for consistency with existing patterns
|
|
1331
|
+
print(f"DEBUG: {operation_name} called for {model_cls.__name__} with {len(objs)} objects{param_str}")
|
|
1332
|
+
logger.debug(f"{operation_name} called for {model_cls.__name__} with {len(objs)} objects{param_str}")
|
|
1333
|
+
|
|
1334
|
+
def _execute_delete_hooks_with_operation(self, operation_func, objs, ctx=None, bypass_hooks=False, bypass_validation=False):
|
|
1335
|
+
"""
|
|
1336
|
+
Execute hooks for delete operations with special field caching logic.
|
|
1337
|
+
|
|
1338
|
+
Args:
|
|
1339
|
+
operation_func (callable): The delete operation to execute
|
|
1340
|
+
objs (list): Objects being deleted
|
|
1341
|
+
ctx: Hook context
|
|
1342
|
+
bypass_hooks (bool): Whether to skip hooks
|
|
1343
|
+
bypass_validation (bool): Whether to skip validation hooks
|
|
1344
|
+
|
|
1345
|
+
Returns:
|
|
1346
|
+
The result of the delete operation
|
|
1347
|
+
"""
|
|
1348
|
+
model_cls = self.model
|
|
1349
|
+
|
|
1350
|
+
# Run validation hooks first (if not bypassed)
|
|
1351
|
+
if not bypass_validation:
|
|
1352
|
+
engine.run(model_cls, VALIDATE_DELETE, objs, ctx=ctx)
|
|
1353
|
+
|
|
1354
|
+
# Run before hooks (if not bypassed)
|
|
1355
|
+
if not bypass_hooks:
|
|
1356
|
+
engine.run(model_cls, BEFORE_DELETE, objs, ctx=ctx)
|
|
1357
|
+
|
|
1358
|
+
# Before deletion, ensure all related fields are properly cached
|
|
1359
|
+
# to avoid DoesNotExist errors in AFTER_DELETE hooks
|
|
1360
|
+
for obj in objs:
|
|
1361
|
+
if obj.pk is not None:
|
|
1362
|
+
# Cache all foreign key relationships by accessing them
|
|
1363
|
+
for field in model_cls._meta.fields:
|
|
1364
|
+
if (
|
|
1365
|
+
field.is_relation
|
|
1366
|
+
and not field.many_to_many
|
|
1367
|
+
and not field.one_to_many
|
|
1368
|
+
):
|
|
1369
|
+
try:
|
|
1370
|
+
# Access the related field to cache it before deletion
|
|
1371
|
+
getattr(obj, field.name)
|
|
1372
|
+
except Exception:
|
|
1373
|
+
# If we can't access the field (e.g., already deleted, no permission, etc.)
|
|
1374
|
+
# continue with other fields
|
|
1375
|
+
pass
|
|
1376
|
+
|
|
1377
|
+
# Execute the database operation
|
|
1378
|
+
result = operation_func()
|
|
1379
|
+
|
|
1380
|
+
# Run after hooks (if not bypassed)
|
|
1381
|
+
if not bypass_hooks:
|
|
1382
|
+
engine.run(model_cls, AFTER_DELETE, objs, ctx=ctx)
|
|
1383
|
+
|
|
1384
|
+
return result
|
|
1385
|
+
|
|
1386
|
+
def _setup_bulk_operation(self, objs, operation_name, require_pks=False, bypass_hooks=False, bypass_validation=False, **log_kwargs):
|
|
1387
|
+
"""
|
|
1388
|
+
Common setup logic for bulk operations.
|
|
1389
|
+
|
|
1390
|
+
Args:
|
|
1391
|
+
objs (list): Objects to operate on
|
|
1392
|
+
operation_name (str): Name of the operation for logging and validation
|
|
1393
|
+
require_pks (bool): Whether objects must have primary keys
|
|
1394
|
+
bypass_hooks (bool): Whether to bypass hooks
|
|
1395
|
+
bypass_validation (bool): Whether to bypass validation
|
|
1396
|
+
**log_kwargs: Additional parameters to log
|
|
1397
|
+
|
|
1398
|
+
Returns:
|
|
1399
|
+
tuple: (model_cls, ctx, originals)
|
|
1400
|
+
"""
|
|
1401
|
+
# Log operation start
|
|
1402
|
+
self._log_bulk_operation_start(operation_name, objs, **log_kwargs)
|
|
1403
|
+
|
|
1404
|
+
# Validate objects
|
|
1405
|
+
self._validate_objects(objs, require_pks=require_pks, operation_name=operation_name)
|
|
1406
|
+
|
|
1407
|
+
# Initialize hook context
|
|
1408
|
+
ctx, originals = self._init_hook_context(bypass_hooks, objs, operation_name)
|
|
1409
|
+
|
|
1410
|
+
return self.model, ctx, originals
|
|
1411
|
+
|
|
1263
1412
|
def _is_multi_table_inheritance(self) -> bool:
|
|
1264
1413
|
"""
|
|
1265
1414
|
Determine whether this model uses multi-table inheritance (MTI).
|
|
@@ -1271,7 +1420,7 @@ class HookQuerySetMixin:
|
|
|
1271
1420
|
logger.debug(
|
|
1272
1421
|
"%s detected as MTI model (parent: %s)",
|
|
1273
1422
|
model_cls.__name__,
|
|
1274
|
-
parent
|
|
1423
|
+
getattr(parent, "__name__", str(parent)),
|
|
1275
1424
|
)
|
|
1276
1425
|
return True
|
|
1277
1426
|
|
|
@@ -1793,56 +1942,24 @@ class HookQuerySetMixin:
|
|
|
1793
1942
|
if not objs:
|
|
1794
1943
|
return 0
|
|
1795
1944
|
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
)
|
|
1800
|
-
|
|
1801
|
-
logger.debug(
|
|
1802
|
-
f"bulk_delete {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}"
|
|
1945
|
+
model_cls, ctx, _ = self._setup_bulk_operation(
|
|
1946
|
+
objs, "bulk_delete", require_pks=True,
|
|
1947
|
+
bypass_hooks=bypass_hooks, bypass_validation=bypass_validation
|
|
1803
1948
|
)
|
|
1804
1949
|
|
|
1805
|
-
#
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
if
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
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
|
|
1950
|
+
# Execute the database operation with hooks
|
|
1951
|
+
def delete_operation():
|
|
1952
|
+
pks = [obj.pk for obj in objs if obj.pk is not None]
|
|
1953
|
+
if pks:
|
|
1954
|
+
# Use the base manager to avoid recursion
|
|
1955
|
+
return self.model._base_manager.filter(pk__in=pks).delete()[0]
|
|
1956
|
+
else:
|
|
1957
|
+
return 0
|
|
1842
1958
|
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1959
|
+
result = self._execute_delete_hooks_with_operation(
|
|
1960
|
+
delete_operation, objs, ctx=ctx,
|
|
1961
|
+
bypass_hooks=bypass_hooks, bypass_validation=bypass_validation
|
|
1962
|
+
)
|
|
1846
1963
|
|
|
1847
1964
|
return result
|
|
1848
1965
|
|
|
@@ -9,9 +9,9 @@ django_bulk_hooks/handler.py,sha256=Bx-W6yyiciKMyy-BRxUt3CmRPCrX9_LhQgU-5LaJTjg,
|
|
|
9
9
|
django_bulk_hooks/manager.py,sha256=3jNWL-EkvGScsliNc7mW-ozQCG6HyaEevI1u1BFS4AA,3836
|
|
10
10
|
django_bulk_hooks/models.py,sha256=WtSfc4GBOG_oOt8n37cVvid0MtFIGze9JYKSixil2y0,4370
|
|
11
11
|
django_bulk_hooks/priority.py,sha256=HG_2D35nga68lBCZmSXTcplXrjFoRgZFRDOy4ROKonY,376
|
|
12
|
-
django_bulk_hooks/queryset.py,sha256=
|
|
12
|
+
django_bulk_hooks/queryset.py,sha256=qgBwrlOeoK70QDFT5dAPoGttKCkkrAxhaIMs4qSI4A4,89278
|
|
13
13
|
django_bulk_hooks/registry.py,sha256=GRUTGVQEO2sdkC9OaZ9Q3U7mM-3Ix83uTyvrlTtpatw,1317
|
|
14
|
-
django_bulk_hooks-0.1.
|
|
15
|
-
django_bulk_hooks-0.1.
|
|
16
|
-
django_bulk_hooks-0.1.
|
|
17
|
-
django_bulk_hooks-0.1.
|
|
14
|
+
django_bulk_hooks-0.1.268.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
15
|
+
django_bulk_hooks-0.1.268.dist-info/METADATA,sha256=Sw7iTHbdn_v2TAEdrKSdazlC_wc6D-hK-w_PFrn9F7Y,9115
|
|
16
|
+
django_bulk_hooks-0.1.268.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
17
|
+
django_bulk_hooks-0.1.268.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|