django-bulk-hooks 0.1.184__py3-none-any.whl → 0.1.187__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 +116 -11
- {django_bulk_hooks-0.1.184.dist-info → django_bulk_hooks-0.1.187.dist-info}/METADATA +1 -1
- {django_bulk_hooks-0.1.184.dist-info → django_bulk_hooks-0.1.187.dist-info}/RECORD +5 -5
- {django_bulk_hooks-0.1.184.dist-info → django_bulk_hooks-0.1.187.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.1.184.dist-info → django_bulk_hooks-0.1.187.dist-info}/WHEEL +0 -0
django_bulk_hooks/queryset.py
CHANGED
|
@@ -178,9 +178,17 @@ class HookQuerySet(models.QuerySet):
|
|
|
178
178
|
"""
|
|
179
179
|
Bulk update objects in the database with MTI support.
|
|
180
180
|
"""
|
|
181
|
+
print(f"\n=== BULK UPDATE DEBUG ===")
|
|
182
|
+
print(f"Model: {self.model.__name__}")
|
|
183
|
+
print(f"Number of objects: {len(objs)}")
|
|
184
|
+
print(f"Fields: {fields}")
|
|
185
|
+
print(f"Bypass hooks: {bypass_hooks}")
|
|
186
|
+
print(f"Bypass validation: {bypass_validation}")
|
|
187
|
+
|
|
181
188
|
model_cls = self.model
|
|
182
189
|
|
|
183
190
|
if not objs:
|
|
191
|
+
print("No objects to update")
|
|
184
192
|
return []
|
|
185
193
|
|
|
186
194
|
if any(not isinstance(obj, model_cls) for obj in objs):
|
|
@@ -194,6 +202,11 @@ class HookQuerySet(models.QuerySet):
|
|
|
194
202
|
if parent._meta.concrete_model is not model_cls._meta.concrete_model:
|
|
195
203
|
is_mti = True
|
|
196
204
|
break
|
|
205
|
+
|
|
206
|
+
print(f"Is MTI: {is_mti}")
|
|
207
|
+
print(f"Model concrete model: {model_cls._meta.concrete_model.__name__}")
|
|
208
|
+
for parent in model_cls._meta.all_parents:
|
|
209
|
+
print(f" Parent {parent.__name__}: concrete_model = {parent._meta.concrete_model.__name__}")
|
|
197
210
|
|
|
198
211
|
if not bypass_hooks:
|
|
199
212
|
# Load originals for hook comparison
|
|
@@ -220,11 +233,14 @@ class HookQuerySet(models.QuerySet):
|
|
|
220
233
|
fields_set = set(fields)
|
|
221
234
|
fields_set.update(modified_fields)
|
|
222
235
|
fields = list(fields_set)
|
|
236
|
+
print(f"Modified fields detected: {modified_fields}")
|
|
223
237
|
|
|
224
238
|
# Handle MTI models differently
|
|
225
239
|
if is_mti:
|
|
240
|
+
print("Using MTI bulk update logic")
|
|
226
241
|
result = self._mti_bulk_update(objs, fields, **kwargs)
|
|
227
242
|
else:
|
|
243
|
+
print("Using standard Django bulk_update")
|
|
228
244
|
# For single-table models, use Django's built-in bulk_update
|
|
229
245
|
django_kwargs = {
|
|
230
246
|
k: v
|
|
@@ -236,6 +252,7 @@ class HookQuerySet(models.QuerySet):
|
|
|
236
252
|
if not bypass_hooks:
|
|
237
253
|
engine.run(model_cls, AFTER_UPDATE, objs, originals, ctx=ctx)
|
|
238
254
|
|
|
255
|
+
print(f"Bulk update result: {result}")
|
|
239
256
|
return result
|
|
240
257
|
|
|
241
258
|
def _detect_modified_fields(self, new_instances, original_instances):
|
|
@@ -280,18 +297,29 @@ class HookQuerySet(models.QuerySet):
|
|
|
280
297
|
Get the complete inheritance chain from root parent to current model.
|
|
281
298
|
Returns list of model classes in order: [RootParent, Parent, Child]
|
|
282
299
|
"""
|
|
300
|
+
print(f"\n=== GET INHERITANCE CHAIN DEBUG ===")
|
|
301
|
+
print(f"Current model: {self.model.__name__}")
|
|
302
|
+
|
|
283
303
|
chain = []
|
|
284
304
|
current_model = self.model
|
|
285
305
|
while current_model:
|
|
306
|
+
print(f"Processing model: {current_model.__name__}")
|
|
286
307
|
if not current_model._meta.proxy:
|
|
287
308
|
chain.append(current_model)
|
|
309
|
+
print(f" Added to chain: {current_model.__name__}")
|
|
310
|
+
else:
|
|
311
|
+
print(f" Skipped proxy model: {current_model.__name__}")
|
|
312
|
+
|
|
288
313
|
parents = [
|
|
289
314
|
parent
|
|
290
315
|
for parent in current_model._meta.parents.keys()
|
|
291
316
|
if not parent._meta.proxy
|
|
292
317
|
]
|
|
318
|
+
print(f" Parents: {[p.__name__ for p in parents]}")
|
|
293
319
|
current_model = parents[0] if parents else None
|
|
320
|
+
|
|
294
321
|
chain.reverse()
|
|
322
|
+
print(f"Final inheritance chain: {[m.__name__ for m in chain]}")
|
|
295
323
|
return chain
|
|
296
324
|
|
|
297
325
|
def _mti_bulk_create(self, objs, inheritance_chain=None, **kwargs):
|
|
@@ -321,13 +349,13 @@ class HookQuerySet(models.QuerySet):
|
|
|
321
349
|
with transaction.atomic(using=self.db, savepoint=False):
|
|
322
350
|
for i in range(0, len(objs), batch_size):
|
|
323
351
|
batch = objs[i : i + batch_size]
|
|
324
|
-
batch_result = self.
|
|
352
|
+
batch_result = self._process_mti_bulk_create_batch(
|
|
325
353
|
batch, inheritance_chain, **django_kwargs
|
|
326
354
|
)
|
|
327
355
|
created_objects.extend(batch_result)
|
|
328
356
|
return created_objects
|
|
329
357
|
|
|
330
|
-
def
|
|
358
|
+
def _process_mti_bulk_create_batch(self, batch, inheritance_chain, **kwargs):
|
|
331
359
|
"""
|
|
332
360
|
Process a single batch of objects through the inheritance chain.
|
|
333
361
|
Implements Django's suggested workaround #2: O(n) normal inserts into parent
|
|
@@ -534,8 +562,27 @@ class HookQuerySet(models.QuerySet):
|
|
|
534
562
|
Custom bulk update implementation for MTI models.
|
|
535
563
|
Updates each table in the inheritance chain efficiently using Django's batch_size.
|
|
536
564
|
"""
|
|
565
|
+
print(f"\n=== MTI BULK UPDATE DEBUG ===")
|
|
566
|
+
print(f"Model: {self.model.__name__}")
|
|
567
|
+
print(f"Number of objects: {len(objs)}")
|
|
568
|
+
print(f"Fields to update: {fields}")
|
|
569
|
+
|
|
537
570
|
model_cls = self.model
|
|
538
571
|
inheritance_chain = self._get_inheritance_chain()
|
|
572
|
+
print(f"Inheritance chain: {[m.__name__ for m in inheritance_chain]}")
|
|
573
|
+
|
|
574
|
+
# Remove custom hook kwargs before passing to Django internals
|
|
575
|
+
django_kwargs = {
|
|
576
|
+
k: v
|
|
577
|
+
for k, v in kwargs.items()
|
|
578
|
+
if k not in ["bypass_hooks", "bypass_validation"]
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
# Safety check to prevent infinite recursion
|
|
582
|
+
if len(inheritance_chain) > 10: # Arbitrary limit to prevent infinite loops
|
|
583
|
+
raise ValueError(
|
|
584
|
+
"Inheritance chain too deep - possible infinite recursion detected"
|
|
585
|
+
)
|
|
539
586
|
|
|
540
587
|
# Group fields by model in the inheritance chain
|
|
541
588
|
field_groups = {}
|
|
@@ -547,12 +594,47 @@ class HookQuerySet(models.QuerySet):
|
|
|
547
594
|
if model not in field_groups:
|
|
548
595
|
field_groups[model] = []
|
|
549
596
|
field_groups[model].append(field_name)
|
|
597
|
+
print(f"Field '{field_name}' belongs to model '{model.__name__}'")
|
|
550
598
|
break
|
|
599
|
+
|
|
600
|
+
print(f"Field groups: {field_groups}")
|
|
551
601
|
|
|
552
|
-
#
|
|
602
|
+
# Process in batches
|
|
603
|
+
batch_size = django_kwargs.get("batch_size") or len(objs)
|
|
553
604
|
total_updated = 0
|
|
605
|
+
|
|
606
|
+
print(f"Processing in batches of size: {batch_size}")
|
|
607
|
+
|
|
608
|
+
with transaction.atomic(using=self.db, savepoint=False):
|
|
609
|
+
for i in range(0, len(objs), batch_size):
|
|
610
|
+
batch = objs[i : i + batch_size]
|
|
611
|
+
print(f"\n--- Processing batch {i//batch_size + 1} ({len(batch)} objects) ---")
|
|
612
|
+
batch_result = self._process_mti_bulk_update_batch(
|
|
613
|
+
batch, field_groups, inheritance_chain, **django_kwargs
|
|
614
|
+
)
|
|
615
|
+
total_updated += batch_result
|
|
616
|
+
print(f"Batch {i//batch_size + 1} updated {batch_result} rows")
|
|
617
|
+
|
|
618
|
+
print(f"\n=== TOTAL UPDATED: {total_updated} ===")
|
|
619
|
+
return total_updated
|
|
620
|
+
|
|
621
|
+
def _process_mti_bulk_update_batch(self, batch, field_groups, inheritance_chain, **kwargs):
|
|
622
|
+
"""
|
|
623
|
+
Process a single batch of objects for MTI bulk update.
|
|
624
|
+
Updates each table in the inheritance chain for the batch.
|
|
625
|
+
"""
|
|
626
|
+
total_updated = 0
|
|
627
|
+
|
|
628
|
+
print(f"Processing batch with {len(batch)} objects")
|
|
629
|
+
print(f"Field groups: {field_groups}")
|
|
630
|
+
|
|
631
|
+
# Update each table in the inheritance chain
|
|
554
632
|
for model, model_fields in field_groups.items():
|
|
633
|
+
print(f"\n--- Updating model: {model.__name__} ---")
|
|
634
|
+
print(f"Fields to update: {model_fields}")
|
|
635
|
+
|
|
555
636
|
if not model_fields:
|
|
637
|
+
print("No fields to update, skipping")
|
|
556
638
|
continue
|
|
557
639
|
|
|
558
640
|
# For MTI, we need to handle parent links correctly
|
|
@@ -560,8 +642,9 @@ class HookQuerySet(models.QuerySet):
|
|
|
560
642
|
# Child models use the parent link to reference the root PK
|
|
561
643
|
if model == inheritance_chain[0]:
|
|
562
644
|
# Root model - use primary keys directly
|
|
563
|
-
pks = [obj.pk for obj in
|
|
645
|
+
pks = [obj.pk for obj in batch]
|
|
564
646
|
filter_field = 'pk'
|
|
647
|
+
print(f"Root model - using PKs: {pks}")
|
|
565
648
|
else:
|
|
566
649
|
# Child model - use parent link field
|
|
567
650
|
parent_link = None
|
|
@@ -571,30 +654,52 @@ class HookQuerySet(models.QuerySet):
|
|
|
571
654
|
break
|
|
572
655
|
|
|
573
656
|
if parent_link is None:
|
|
574
|
-
|
|
657
|
+
print(f"No parent link found for {model.__name__}, skipping")
|
|
575
658
|
continue
|
|
576
659
|
|
|
660
|
+
print(f"Parent link field: {parent_link.name} ({parent_link.attname})")
|
|
661
|
+
|
|
577
662
|
# Get the parent link values (these should be the same as the root PKs)
|
|
578
|
-
pks = [getattr(obj, parent_link.attname) for obj in
|
|
663
|
+
pks = [getattr(obj, parent_link.attname) for obj in batch]
|
|
579
664
|
filter_field = parent_link.attname
|
|
665
|
+
print(f"Child model - using parent link values: {pks}")
|
|
580
666
|
|
|
581
667
|
if pks:
|
|
582
668
|
base_qs = model._base_manager.using(self.db)
|
|
669
|
+
print(f"Filter field: {filter_field}")
|
|
670
|
+
print(f"PKs to filter by: {pks}")
|
|
671
|
+
|
|
672
|
+
# Check if records exist
|
|
673
|
+
existing_count = base_qs.filter(**{f"{filter_field}__in": pks}).count()
|
|
674
|
+
print(f"Existing records with these PKs: {existing_count}")
|
|
583
675
|
|
|
584
|
-
# Build CASE statements for each field
|
|
676
|
+
# Build CASE statements for each field to perform a single bulk update
|
|
585
677
|
case_statements = {}
|
|
586
678
|
for field_name in model_fields:
|
|
587
679
|
field = model._meta.get_field(field_name)
|
|
588
680
|
when_statements = []
|
|
589
681
|
|
|
590
|
-
|
|
682
|
+
print(f"Building CASE statement for field: {field_name}")
|
|
683
|
+
for pk, obj in zip(pks, batch):
|
|
591
684
|
value = getattr(obj, field_name)
|
|
685
|
+
print(f" PK {pk}: {field_name} = {value}")
|
|
592
686
|
when_statements.append(When(**{filter_field: pk}, then=Value(value, output_field=field)))
|
|
593
687
|
|
|
594
688
|
case_statements[field_name] = Case(*when_statements, output_field=field)
|
|
595
689
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
690
|
+
print(f"Case statements built: {list(case_statements.keys())}")
|
|
691
|
+
|
|
692
|
+
# Execute a single bulk update for all objects in this model
|
|
693
|
+
try:
|
|
694
|
+
updated_count = base_qs.filter(**{f"{filter_field}__in": pks}).update(**case_statements)
|
|
695
|
+
print(f"UPDATE QUERY EXECUTED - Updated {updated_count} rows")
|
|
696
|
+
total_updated += updated_count
|
|
697
|
+
except Exception as e:
|
|
698
|
+
print(f"ERROR during update: {e}")
|
|
699
|
+
import traceback
|
|
700
|
+
traceback.print_exc()
|
|
701
|
+
else:
|
|
702
|
+
print("No PKs found, skipping update")
|
|
599
703
|
|
|
704
|
+
print(f"Batch total updated: {total_updated}")
|
|
600
705
|
return total_updated
|
|
@@ -9,9 +9,9 @@ django_bulk_hooks/handler.py,sha256=xZt8iNdYF-ACz-MnKMY0co6scWINU5V5wC1lyDn844k,
|
|
|
9
9
|
django_bulk_hooks/manager.py,sha256=KLEjpQRt4WlzgBAf_X3XOAPUQM8Jmc1fIt8yr62FPQc,3044
|
|
10
10
|
django_bulk_hooks/models.py,sha256=7fnx5xd4HWXfLVlFhhiRzR92JRWFEuxgk6aSWLEsyJg,3996
|
|
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=DYt_oK8g7D1oHGIT9gzE--F9qdXKaMEgPJq5x-t9cSw,30664
|
|
13
13
|
django_bulk_hooks/registry.py,sha256=-mQBizJ06nz_tajZBinViKx_uP2Tbc1tIpTEMv7lwKA,705
|
|
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.187.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
15
|
+
django_bulk_hooks-0.1.187.dist-info/METADATA,sha256=TgXRIiryaRvkIg_IJ5l9xRaGCwkQqsAahFpNff_eymQ,6951
|
|
16
|
+
django_bulk_hooks-0.1.187.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
17
|
+
django_bulk_hooks-0.1.187.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|