django-bulk-hooks 0.1.254__tar.gz → 0.1.256__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.
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/PKG-INFO +1 -1
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/django_bulk_hooks/queryset.py +56 -12
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/pyproject.toml +1 -1
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/LICENSE +0 -0
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/README.md +0 -0
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/django_bulk_hooks/__init__.py +0 -0
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/django_bulk_hooks/conditions.py +0 -0
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/django_bulk_hooks/constants.py +0 -0
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/django_bulk_hooks/context.py +0 -0
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/django_bulk_hooks/decorators.py +0 -0
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/django_bulk_hooks/engine.py +0 -0
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/django_bulk_hooks/enums.py +0 -0
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/django_bulk_hooks/handler.py +0 -0
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/django_bulk_hooks/manager.py +0 -0
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/django_bulk_hooks/models.py +0 -0
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/django_bulk_hooks/priority.py +0 -0
- {django_bulk_hooks-0.1.254 → django_bulk_hooks-0.1.256}/django_bulk_hooks/registry.py +0 -0
|
@@ -627,17 +627,24 @@ class HookQuerySetMixin:
|
|
|
627
627
|
# CRITICAL: Exclude auto_now fields from update_fields for existing records
|
|
628
628
|
# This prevents Django from including them in the ON CONFLICT DO UPDATE clause
|
|
629
629
|
if existing_records and update_fields:
|
|
630
|
+
logger.debug(f"Processing {len(existing_records)} existing records with update_fields: {update_fields}")
|
|
631
|
+
|
|
630
632
|
# Identify auto_now fields that should be excluded from updates
|
|
631
633
|
auto_now_fields_to_exclude = set()
|
|
632
634
|
for field in model_cls._meta.local_fields:
|
|
633
635
|
if hasattr(field, "auto_now") and field.auto_now:
|
|
634
636
|
auto_now_fields_to_exclude.add(field.name)
|
|
635
637
|
|
|
638
|
+
logger.debug(f"Found auto_now fields: {auto_now_fields_to_exclude}")
|
|
639
|
+
|
|
636
640
|
# Filter out auto_now fields from update_fields for existing records
|
|
637
641
|
if auto_now_fields_to_exclude:
|
|
638
642
|
# Create a filtered version of update_fields that excludes auto_now fields
|
|
639
643
|
filtered_update_fields = [f for f in update_fields if f not in auto_now_fields_to_exclude]
|
|
640
644
|
|
|
645
|
+
logger.debug(f"Filtered update_fields: {filtered_update_fields}")
|
|
646
|
+
logger.debug(f"Excluded auto_now fields: {auto_now_fields_to_exclude}")
|
|
647
|
+
|
|
641
648
|
# Store the original update_fields to restore later
|
|
642
649
|
ctx.original_update_fields = update_fields
|
|
643
650
|
ctx.auto_now_fields_excluded = auto_now_fields_to_exclude
|
|
@@ -645,6 +652,12 @@ class HookQuerySetMixin:
|
|
|
645
652
|
# Use the filtered update_fields for the database operation
|
|
646
653
|
# This prevents Django from overwriting the timestamps during upsert
|
|
647
654
|
update_fields = filtered_update_fields
|
|
655
|
+
|
|
656
|
+
logger.debug(f"Final update_fields for DB operation: {update_fields}")
|
|
657
|
+
else:
|
|
658
|
+
logger.debug("No auto_now fields found to exclude")
|
|
659
|
+
else:
|
|
660
|
+
logger.debug(f"No existing records or update_fields to process. existing_records: {len(existing_records) if existing_records else 0}, update_fields: {update_fields}")
|
|
648
661
|
|
|
649
662
|
# Run validation hooks on all records
|
|
650
663
|
if not bypass_validation:
|
|
@@ -694,6 +707,10 @@ class HookQuerySetMixin:
|
|
|
694
707
|
# but we need to call it on the base manager to avoid recursion
|
|
695
708
|
# Filter out custom parameters that Django's bulk_create doesn't accept
|
|
696
709
|
|
|
710
|
+
logger.debug(f"Calling Django bulk_create with update_fields: {update_fields}")
|
|
711
|
+
logger.debug(f"Calling Django bulk_create with update_conflicts: {update_conflicts}")
|
|
712
|
+
logger.debug(f"Calling Django bulk_create with unique_fields: {unique_fields}")
|
|
713
|
+
|
|
697
714
|
result = super().bulk_create(
|
|
698
715
|
objs,
|
|
699
716
|
batch_size=batch_size,
|
|
@@ -702,15 +719,19 @@ class HookQuerySetMixin:
|
|
|
702
719
|
update_fields=update_fields,
|
|
703
720
|
unique_fields=unique_fields,
|
|
704
721
|
)
|
|
722
|
+
|
|
723
|
+
logger.debug(f"Django bulk_create completed with result: {result}")
|
|
705
724
|
|
|
706
725
|
# Fire AFTER hooks
|
|
707
726
|
if not bypass_hooks:
|
|
708
727
|
if update_conflicts and unique_fields:
|
|
709
728
|
# Restore original update_fields if we modified them
|
|
710
729
|
if hasattr(ctx, 'original_update_fields'):
|
|
730
|
+
logger.debug(f"Restoring original update_fields: {ctx.original_update_fields}")
|
|
711
731
|
update_fields = ctx.original_update_fields
|
|
712
732
|
delattr(ctx, 'original_update_fields')
|
|
713
733
|
delattr(ctx, 'auto_now_fields_excluded')
|
|
734
|
+
logger.debug(f"Restored update_fields: {update_fields}")
|
|
714
735
|
|
|
715
736
|
# For upsert operations, we need to determine which records were actually created vs updated
|
|
716
737
|
# Use the same logic as before to separate records
|
|
@@ -794,7 +815,7 @@ class HookQuerySetMixin:
|
|
|
794
815
|
)
|
|
795
816
|
|
|
796
817
|
logger.debug(
|
|
797
|
-
f"bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}"
|
|
818
|
+
f"bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)} fields={fields}"
|
|
798
819
|
)
|
|
799
820
|
|
|
800
821
|
# Check for MTI
|
|
@@ -818,15 +839,35 @@ class HookQuerySetMixin:
|
|
|
818
839
|
# Handle auto_now fields like Django's update_or_create does
|
|
819
840
|
fields_set = set(fields)
|
|
820
841
|
pk_fields = model_cls._meta.pk_fields
|
|
842
|
+
auto_now_fields = []
|
|
843
|
+
logger.debug(f"Checking for auto_now fields in {model_cls.__name__}")
|
|
821
844
|
for field in model_cls._meta.local_concrete_fields:
|
|
822
845
|
# Only add auto_now fields (like updated_at) that aren't already in the fields list
|
|
823
846
|
# Don't include auto_now_add fields (like created_at) as they should only be set on creation
|
|
824
847
|
if hasattr(field, "auto_now") and field.auto_now:
|
|
848
|
+
logger.debug(f"Found auto_now field: {field.name}")
|
|
825
849
|
if field.name not in fields_set and field.name not in pk_fields:
|
|
826
850
|
fields_set.add(field.name)
|
|
827
851
|
if field.name != field.attname:
|
|
828
852
|
fields_set.add(field.attname)
|
|
853
|
+
auto_now_fields.append(field.name)
|
|
854
|
+
logger.debug(f"Added auto_now field {field.name} to fields list")
|
|
855
|
+
else:
|
|
856
|
+
logger.debug(f"Auto_now field {field.name} already in fields list or is PK")
|
|
857
|
+
elif hasattr(field, "auto_now_add") and field.auto_now_add:
|
|
858
|
+
logger.debug(f"Found auto_now_add field: {field.name} (skipping)")
|
|
859
|
+
|
|
860
|
+
logger.debug(f"Auto_now fields detected: {auto_now_fields}")
|
|
829
861
|
fields = list(fields_set)
|
|
862
|
+
|
|
863
|
+
# Set auto_now field values to current timestamp
|
|
864
|
+
if auto_now_fields:
|
|
865
|
+
from django.utils import timezone
|
|
866
|
+
current_time = timezone.now()
|
|
867
|
+
logger.debug(f"Setting auto_now fields {auto_now_fields} to current time: {current_time}")
|
|
868
|
+
for obj in objs:
|
|
869
|
+
for field_name in auto_now_fields:
|
|
870
|
+
setattr(obj, field_name, current_time)
|
|
830
871
|
|
|
831
872
|
# Handle MTI models differently
|
|
832
873
|
if is_mti:
|
|
@@ -839,17 +880,20 @@ class HookQuerySetMixin:
|
|
|
839
880
|
if k not in ["bypass_hooks", "bypass_validation"]
|
|
840
881
|
}
|
|
841
882
|
logger.debug("Calling Django bulk_update")
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
883
|
+
# Build a per-object concrete value map to avoid leaking expressions into hooks
|
|
884
|
+
value_map = {}
|
|
885
|
+
logger.debug(f"Building value map for {len(objs)} objects with fields: {fields}")
|
|
886
|
+
for obj in objs:
|
|
887
|
+
if obj.pk is None:
|
|
888
|
+
continue
|
|
889
|
+
field_values = {}
|
|
890
|
+
for field_name in fields:
|
|
891
|
+
# Capture raw values assigned on the object (not expressions)
|
|
892
|
+
field_values[field_name] = getattr(obj, field_name)
|
|
893
|
+
if field_name in auto_now_fields:
|
|
894
|
+
logger.debug(f"Object {obj.pk} {field_name}: {field_values[field_name]}")
|
|
895
|
+
if field_values:
|
|
896
|
+
value_map[obj.pk] = field_values
|
|
853
897
|
|
|
854
898
|
# Make the value map available to the subsequent update() call
|
|
855
899
|
if value_map:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "django-bulk-hooks"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.256"
|
|
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"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|