django-bulk-hooks 0.1.191__py3-none-any.whl → 0.1.193__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 +19 -71
- {django_bulk_hooks-0.1.191.dist-info → django_bulk_hooks-0.1.193.dist-info}/METADATA +1 -1
- {django_bulk_hooks-0.1.191.dist-info → django_bulk_hooks-0.1.193.dist-info}/RECORD +5 -5
- {django_bulk_hooks-0.1.191.dist-info → django_bulk_hooks-0.1.193.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.1.191.dist-info → django_bulk_hooks-0.1.193.dist-info}/WHEEL +0 -0
django_bulk_hooks/queryset.py
CHANGED
|
@@ -14,7 +14,7 @@ from django_bulk_hooks.constants import (
|
|
|
14
14
|
VALIDATE_UPDATE,
|
|
15
15
|
)
|
|
16
16
|
from django_bulk_hooks.context import HookContext
|
|
17
|
-
from django.db.models import When, Value, Case
|
|
17
|
+
from django.db.models import When, Value, Case, Field
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class HookQuerySetMixin:
|
|
@@ -183,17 +183,9 @@ class HookQuerySetMixin:
|
|
|
183
183
|
"""
|
|
184
184
|
Bulk update objects in the database with MTI support.
|
|
185
185
|
"""
|
|
186
|
-
print(f"\n=== BULK UPDATE DEBUG ===")
|
|
187
|
-
print(f"Model: {self.model.__name__}")
|
|
188
|
-
print(f"Number of objects: {len(objs)}")
|
|
189
|
-
print(f"Fields: {fields}")
|
|
190
|
-
print(f"Bypass hooks: {bypass_hooks}")
|
|
191
|
-
print(f"Bypass validation: {bypass_validation}")
|
|
192
|
-
|
|
193
186
|
model_cls = self.model
|
|
194
187
|
|
|
195
188
|
if not objs:
|
|
196
|
-
print("No objects to update")
|
|
197
189
|
return []
|
|
198
190
|
|
|
199
191
|
if any(not isinstance(obj, model_cls) for obj in objs):
|
|
@@ -207,11 +199,6 @@ class HookQuerySetMixin:
|
|
|
207
199
|
if parent._meta.concrete_model is not model_cls._meta.concrete_model:
|
|
208
200
|
is_mti = True
|
|
209
201
|
break
|
|
210
|
-
|
|
211
|
-
print(f"Is MTI: {is_mti}")
|
|
212
|
-
print(f"Model concrete model: {model_cls._meta.concrete_model.__name__}")
|
|
213
|
-
for parent in model_cls._meta.all_parents:
|
|
214
|
-
print(f" Parent {parent.__name__}: concrete_model = {parent._meta.concrete_model.__name__}")
|
|
215
202
|
|
|
216
203
|
if not bypass_hooks:
|
|
217
204
|
# Load originals for hook comparison
|
|
@@ -238,14 +225,23 @@ class HookQuerySetMixin:
|
|
|
238
225
|
fields_set = set(fields)
|
|
239
226
|
fields_set.update(modified_fields)
|
|
240
227
|
fields = list(fields_set)
|
|
241
|
-
|
|
228
|
+
|
|
229
|
+
# Handle auto_now fields like Django's update_or_create does
|
|
230
|
+
fields_set = set(fields)
|
|
231
|
+
pk_fields = model_cls._meta.pk_fields
|
|
232
|
+
for field in model_cls._meta.local_concrete_fields:
|
|
233
|
+
if not (
|
|
234
|
+
field in pk_fields or field.__class__.pre_save is Field.pre_save
|
|
235
|
+
):
|
|
236
|
+
fields_set.add(field.name)
|
|
237
|
+
if field.name != field.attname:
|
|
238
|
+
fields_set.add(field.attname)
|
|
239
|
+
fields = list(fields_set)
|
|
242
240
|
|
|
243
241
|
# Handle MTI models differently
|
|
244
242
|
if is_mti:
|
|
245
|
-
print("Using MTI bulk update logic")
|
|
246
243
|
result = self._mti_bulk_update(objs, fields, **kwargs)
|
|
247
244
|
else:
|
|
248
|
-
print("Using standard Django bulk_update")
|
|
249
245
|
# For single-table models, use Django's built-in bulk_update
|
|
250
246
|
django_kwargs = {
|
|
251
247
|
k: v
|
|
@@ -257,7 +253,6 @@ class HookQuerySetMixin:
|
|
|
257
253
|
if not bypass_hooks:
|
|
258
254
|
engine.run(model_cls, AFTER_UPDATE, objs, originals, ctx=ctx)
|
|
259
255
|
|
|
260
|
-
print(f"Bulk update result: {result}")
|
|
261
256
|
return result
|
|
262
257
|
|
|
263
258
|
def _detect_modified_fields(self, new_instances, original_instances):
|
|
@@ -302,29 +297,20 @@ class HookQuerySetMixin:
|
|
|
302
297
|
Get the complete inheritance chain from root parent to current model.
|
|
303
298
|
Returns list of model classes in order: [RootParent, Parent, Child]
|
|
304
299
|
"""
|
|
305
|
-
print(f"\n=== GET INHERITANCE CHAIN DEBUG ===")
|
|
306
|
-
print(f"Current model: {self.model.__name__}")
|
|
307
|
-
|
|
308
300
|
chain = []
|
|
309
301
|
current_model = self.model
|
|
310
302
|
while current_model:
|
|
311
|
-
print(f"Processing model: {current_model.__name__}")
|
|
312
303
|
if not current_model._meta.proxy:
|
|
313
304
|
chain.append(current_model)
|
|
314
|
-
print(f" Added to chain: {current_model.__name__}")
|
|
315
|
-
else:
|
|
316
|
-
print(f" Skipped proxy model: {current_model.__name__}")
|
|
317
305
|
|
|
318
306
|
parents = [
|
|
319
307
|
parent
|
|
320
308
|
for parent in current_model._meta.parents.keys()
|
|
321
309
|
if not parent._meta.proxy
|
|
322
310
|
]
|
|
323
|
-
print(f" Parents: {[p.__name__ for p in parents]}")
|
|
324
311
|
current_model = parents[0] if parents else None
|
|
325
312
|
|
|
326
313
|
chain.reverse()
|
|
327
|
-
print(f"Final inheritance chain: {[m.__name__ for m in chain]}")
|
|
328
314
|
return chain
|
|
329
315
|
|
|
330
316
|
def _mti_bulk_create(self, objs, inheritance_chain=None, **kwargs):
|
|
@@ -567,14 +553,8 @@ class HookQuerySetMixin:
|
|
|
567
553
|
Custom bulk update implementation for MTI models.
|
|
568
554
|
Updates each table in the inheritance chain efficiently using Django's batch_size.
|
|
569
555
|
"""
|
|
570
|
-
print(f"\n=== MTI BULK UPDATE DEBUG ===")
|
|
571
|
-
print(f"Model: {self.model.__name__}")
|
|
572
|
-
print(f"Number of objects: {len(objs)}")
|
|
573
|
-
print(f"Fields to update: {fields}")
|
|
574
|
-
|
|
575
556
|
model_cls = self.model
|
|
576
557
|
inheritance_chain = self._get_inheritance_chain()
|
|
577
|
-
print(f"Inheritance chain: {[m.__name__ for m in inheritance_chain]}")
|
|
578
558
|
|
|
579
559
|
# Remove custom hook kwargs before passing to Django internals
|
|
580
560
|
django_kwargs = {
|
|
@@ -589,6 +569,12 @@ class HookQuerySetMixin:
|
|
|
589
569
|
"Inheritance chain too deep - possible infinite recursion detected"
|
|
590
570
|
)
|
|
591
571
|
|
|
572
|
+
# Handle auto_now fields by calling pre_save on objects
|
|
573
|
+
for obj in objs:
|
|
574
|
+
for field in model_cls._meta.local_fields:
|
|
575
|
+
if hasattr(field, "auto_now") and field.auto_now:
|
|
576
|
+
field.pre_save(obj, add=False)
|
|
577
|
+
|
|
592
578
|
# Group fields by model in the inheritance chain
|
|
593
579
|
field_groups = {}
|
|
594
580
|
for field_name in fields:
|
|
@@ -599,28 +585,20 @@ class HookQuerySetMixin:
|
|
|
599
585
|
if model not in field_groups:
|
|
600
586
|
field_groups[model] = []
|
|
601
587
|
field_groups[model].append(field_name)
|
|
602
|
-
print(f"Field '{field_name}' belongs to model '{model.__name__}'")
|
|
603
588
|
break
|
|
604
|
-
|
|
605
|
-
print(f"Field groups: {field_groups}")
|
|
606
589
|
|
|
607
590
|
# Process in batches
|
|
608
591
|
batch_size = django_kwargs.get("batch_size") or len(objs)
|
|
609
592
|
total_updated = 0
|
|
610
593
|
|
|
611
|
-
print(f"Processing in batches of size: {batch_size}")
|
|
612
|
-
|
|
613
594
|
with transaction.atomic(using=self.db, savepoint=False):
|
|
614
595
|
for i in range(0, len(objs), batch_size):
|
|
615
596
|
batch = objs[i : i + batch_size]
|
|
616
|
-
print(f"\n--- Processing batch {i//batch_size + 1} ({len(batch)} objects) ---")
|
|
617
597
|
batch_result = self._process_mti_bulk_update_batch(
|
|
618
598
|
batch, field_groups, inheritance_chain, **django_kwargs
|
|
619
599
|
)
|
|
620
600
|
total_updated += batch_result
|
|
621
|
-
print(f"Batch {i//batch_size + 1} updated {batch_result} rows")
|
|
622
601
|
|
|
623
|
-
print(f"\n=== TOTAL UPDATED: {total_updated} ===")
|
|
624
602
|
return total_updated
|
|
625
603
|
|
|
626
604
|
def _process_mti_bulk_update_batch(self, batch, field_groups, inheritance_chain, **kwargs):
|
|
@@ -630,9 +608,6 @@ class HookQuerySetMixin:
|
|
|
630
608
|
"""
|
|
631
609
|
total_updated = 0
|
|
632
610
|
|
|
633
|
-
print(f"Processing batch with {len(batch)} objects")
|
|
634
|
-
print(f"Field groups: {field_groups}")
|
|
635
|
-
|
|
636
611
|
# For MTI, we need to handle parent links correctly
|
|
637
612
|
# The root model (first in chain) has its own PK
|
|
638
613
|
# Child models use the parent link to reference the root PK
|
|
@@ -649,31 +624,21 @@ class HookQuerySetMixin:
|
|
|
649
624
|
|
|
650
625
|
if pk_value is not None:
|
|
651
626
|
root_pks.append(pk_value)
|
|
652
|
-
print(f"Found PK {pk_value} for object {obj}")
|
|
653
627
|
else:
|
|
654
|
-
print(f"WARNING: Object {obj} has no primary key (pk={getattr(obj, 'pk', None)}, id={getattr(obj, 'id', None)})")
|
|
655
628
|
continue
|
|
656
629
|
|
|
657
|
-
print(f"Root PKs to update: {root_pks}")
|
|
658
|
-
|
|
659
630
|
if not root_pks:
|
|
660
|
-
print("No valid primary keys found, skipping update")
|
|
661
631
|
return 0
|
|
662
632
|
|
|
663
633
|
# Update each table in the inheritance chain
|
|
664
634
|
for model, model_fields in field_groups.items():
|
|
665
|
-
print(f"\n--- Updating model: {model.__name__} ---")
|
|
666
|
-
print(f"Fields to update: {model_fields}")
|
|
667
|
-
|
|
668
635
|
if not model_fields:
|
|
669
|
-
print("No fields to update, skipping")
|
|
670
636
|
continue
|
|
671
637
|
|
|
672
638
|
if model == inheritance_chain[0]:
|
|
673
639
|
# Root model - use primary keys directly
|
|
674
640
|
pks = root_pks
|
|
675
641
|
filter_field = 'pk'
|
|
676
|
-
print(f"Root model - using PKs: {pks}")
|
|
677
642
|
else:
|
|
678
643
|
# Child model - use parent link field
|
|
679
644
|
parent_link = None
|
|
@@ -683,27 +648,19 @@ class HookQuerySetMixin:
|
|
|
683
648
|
break
|
|
684
649
|
|
|
685
650
|
if parent_link is None:
|
|
686
|
-
print(f"No parent link found for {model.__name__}, skipping")
|
|
687
651
|
continue
|
|
688
652
|
|
|
689
|
-
print(f"Parent link field: {parent_link.name} ({parent_link.attname})")
|
|
690
|
-
|
|
691
653
|
# For child models, the parent link values should be the same as root PKs
|
|
692
654
|
pks = root_pks
|
|
693
655
|
filter_field = parent_link.attname
|
|
694
|
-
print(f"Child model - using parent link values: {pks}")
|
|
695
656
|
|
|
696
657
|
if pks:
|
|
697
658
|
base_qs = model._base_manager.using(self.db)
|
|
698
|
-
print(f"Filter field: {filter_field}")
|
|
699
|
-
print(f"PKs to filter by: {pks}")
|
|
700
659
|
|
|
701
660
|
# Check if records exist
|
|
702
661
|
existing_count = base_qs.filter(**{f"{filter_field}__in": pks}).count()
|
|
703
|
-
print(f"Existing records with these PKs: {existing_count}")
|
|
704
662
|
|
|
705
663
|
if existing_count == 0:
|
|
706
|
-
print("WARNING: No existing records found with these PKs!")
|
|
707
664
|
continue
|
|
708
665
|
|
|
709
666
|
# Build CASE statements for each field to perform a single bulk update
|
|
@@ -712,7 +669,6 @@ class HookQuerySetMixin:
|
|
|
712
669
|
field = model._meta.get_field(field_name)
|
|
713
670
|
when_statements = []
|
|
714
671
|
|
|
715
|
-
print(f"Building CASE statement for field: {field_name}")
|
|
716
672
|
for pk, obj in zip(pks, batch):
|
|
717
673
|
# Check both pk and id attributes for the object
|
|
718
674
|
obj_pk = getattr(obj, 'pk', None)
|
|
@@ -722,26 +678,18 @@ class HookQuerySetMixin:
|
|
|
722
678
|
if obj_pk is None:
|
|
723
679
|
continue
|
|
724
680
|
value = getattr(obj, field_name)
|
|
725
|
-
print(f" PK {pk}: {field_name} = {value}")
|
|
726
681
|
when_statements.append(When(**{filter_field: pk}, then=Value(value, output_field=field)))
|
|
727
682
|
|
|
728
683
|
case_statements[field_name] = Case(*when_statements, output_field=field)
|
|
729
684
|
|
|
730
|
-
print(f"Case statements built: {list(case_statements.keys())}")
|
|
731
|
-
|
|
732
685
|
# Execute a single bulk update for all objects in this model
|
|
733
686
|
try:
|
|
734
687
|
updated_count = base_qs.filter(**{f"{filter_field}__in": pks}).update(**case_statements)
|
|
735
|
-
print(f"UPDATE QUERY EXECUTED - Updated {updated_count} rows")
|
|
736
688
|
total_updated += updated_count
|
|
737
689
|
except Exception as e:
|
|
738
|
-
print(f"ERROR during update: {e}")
|
|
739
690
|
import traceback
|
|
740
691
|
traceback.print_exc()
|
|
741
|
-
else:
|
|
742
|
-
print("No PKs found, skipping update")
|
|
743
692
|
|
|
744
|
-
print(f"Batch total updated: {total_updated}")
|
|
745
693
|
return total_updated
|
|
746
694
|
|
|
747
695
|
|
|
@@ -9,9 +9,9 @@ django_bulk_hooks/handler.py,sha256=xZt8iNdYF-ACz-MnKMY0co6scWINU5V5wC1lyDn844k,
|
|
|
9
9
|
django_bulk_hooks/manager.py,sha256=nfWiwU5-yAoxdnQsUMohxtyCpkV0MBv6X3wmipr9eQY,3697
|
|
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=nIygGty8Ex9F0Iz8CRIIDxkNZeT3M4LF1vgMh8_o1Jc,28678
|
|
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.193.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
15
|
+
django_bulk_hooks-0.1.193.dist-info/METADATA,sha256=XkCe1wym7M3k0k3pcOOYSH6zdVxatdzziz12gDrEvwM,7418
|
|
16
|
+
django_bulk_hooks-0.1.193.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
17
|
+
django_bulk_hooks-0.1.193.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|