django-bulk-hooks 0.1.260__py3-none-any.whl → 0.1.262__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 +64 -43
- {django_bulk_hooks-0.1.260.dist-info → django_bulk_hooks-0.1.262.dist-info}/METADATA +1 -1
- {django_bulk_hooks-0.1.260.dist-info → django_bulk_hooks-0.1.262.dist-info}/RECORD +5 -5
- {django_bulk_hooks-0.1.260.dist-info → django_bulk_hooks-0.1.262.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.1.260.dist-info → django_bulk_hooks-0.1.262.dist-info}/WHEEL +0 -0
django_bulk_hooks/queryset.py
CHANGED
|
@@ -648,38 +648,38 @@ class HookQuerySetMixin:
|
|
|
648
648
|
|
|
649
649
|
# Remove duplicate code since we're now handling this above
|
|
650
650
|
|
|
651
|
-
# CRITICAL:
|
|
652
|
-
#
|
|
651
|
+
# CRITICAL: Handle auto_now fields intelligently for existing records
|
|
652
|
+
# We need to exclude them from Django's ON CONFLICT DO UPDATE clause to prevent
|
|
653
|
+
# Django's default behavior, but still ensure they get updated via pre_save
|
|
653
654
|
if existing_records and update_fields:
|
|
654
655
|
logger.debug(f"Processing {len(existing_records)} existing records with update_fields: {update_fields}")
|
|
655
|
-
|
|
656
|
-
# Identify auto_now fields
|
|
657
|
-
|
|
656
|
+
|
|
657
|
+
# Identify auto_now fields
|
|
658
|
+
auto_now_fields = set()
|
|
658
659
|
for field in model_cls._meta.local_fields:
|
|
659
660
|
if hasattr(field, "auto_now") and field.auto_now:
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
logger.debug(f"Found auto_now fields: {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
# Create a filtered version of update_fields that excludes auto_now fields
|
|
667
|
-
filtered_update_fields = [f for f in update_fields if f not in auto_now_fields_to_exclude]
|
|
668
|
-
|
|
669
|
-
logger.debug(f"Filtered update_fields: {filtered_update_fields}")
|
|
670
|
-
logger.debug(f"Excluded auto_now fields: {auto_now_fields_to_exclude}")
|
|
671
|
-
|
|
672
|
-
# Store the original update_fields to restore later
|
|
661
|
+
auto_now_fields.add(field.name)
|
|
662
|
+
|
|
663
|
+
logger.debug(f"Found auto_now fields: {auto_now_fields}")
|
|
664
|
+
|
|
665
|
+
if auto_now_fields:
|
|
666
|
+
# Store original update_fields and auto_now fields for later restoration
|
|
673
667
|
ctx.original_update_fields = update_fields
|
|
674
|
-
ctx.
|
|
675
|
-
|
|
676
|
-
#
|
|
677
|
-
# This prevents Django from
|
|
668
|
+
ctx.auto_now_fields = auto_now_fields
|
|
669
|
+
|
|
670
|
+
# Filter out auto_now fields from update_fields for the database operation
|
|
671
|
+
# This prevents Django from including them in ON CONFLICT DO UPDATE
|
|
672
|
+
filtered_update_fields = [f for f in update_fields if f not in auto_now_fields]
|
|
673
|
+
|
|
674
|
+
logger.debug(f"Filtered update_fields: {filtered_update_fields}")
|
|
675
|
+
logger.debug(f"Excluded auto_now fields: {auto_now_fields}")
|
|
676
|
+
|
|
677
|
+
# Use filtered update_fields for Django's bulk_create operation
|
|
678
678
|
update_fields = filtered_update_fields
|
|
679
|
-
|
|
679
|
+
|
|
680
680
|
logger.debug(f"Final update_fields for DB operation: {update_fields}")
|
|
681
681
|
else:
|
|
682
|
-
logger.debug("No auto_now fields found to
|
|
682
|
+
logger.debug("No auto_now fields found to handle")
|
|
683
683
|
else:
|
|
684
684
|
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}")
|
|
685
685
|
|
|
@@ -749,14 +749,32 @@ class HookQuerySetMixin:
|
|
|
749
749
|
# Fire AFTER hooks
|
|
750
750
|
if not bypass_hooks:
|
|
751
751
|
if update_conflicts and unique_fields:
|
|
752
|
+
# Handle auto_now fields that were excluded from the main update
|
|
753
|
+
if hasattr(ctx, 'auto_now_fields') and existing_records:
|
|
754
|
+
logger.debug(f"Performing separate update for auto_now fields: {ctx.auto_now_fields}")
|
|
755
|
+
|
|
756
|
+
# Perform a separate bulk_update for the auto_now fields that were set via pre_save
|
|
757
|
+
# This ensures they get saved to the database even though they were excluded from the main upsert
|
|
758
|
+
try:
|
|
759
|
+
# Use Django's base manager to bypass hooks and ensure the update happens
|
|
760
|
+
base_manager = model_cls._base_manager
|
|
761
|
+
auto_now_update_result = base_manager.bulk_update(
|
|
762
|
+
existing_records, list(ctx.auto_now_fields)
|
|
763
|
+
)
|
|
764
|
+
logger.debug(f"Auto_now fields update completed with result: {auto_now_update_result}")
|
|
765
|
+
except Exception as e:
|
|
766
|
+
logger.error(f"Failed to update auto_now fields: {e}")
|
|
767
|
+
# Don't raise the exception - the main operation succeeded
|
|
768
|
+
|
|
752
769
|
# Restore original update_fields if we modified them
|
|
753
770
|
if hasattr(ctx, 'original_update_fields'):
|
|
754
771
|
logger.debug(f"Restoring original update_fields: {ctx.original_update_fields}")
|
|
755
772
|
update_fields = ctx.original_update_fields
|
|
756
773
|
delattr(ctx, 'original_update_fields')
|
|
757
|
-
|
|
774
|
+
if hasattr(ctx, 'auto_now_fields'):
|
|
775
|
+
delattr(ctx, 'auto_now_fields')
|
|
758
776
|
logger.debug(f"Restored update_fields: {update_fields}")
|
|
759
|
-
|
|
777
|
+
|
|
760
778
|
# For upsert operations, reuse the existing/new records determination from BEFORE hooks
|
|
761
779
|
# This avoids duplicate queries and improves performance
|
|
762
780
|
if hasattr(ctx, 'upsert_existing_records') and hasattr(ctx, 'upsert_new_records'):
|
|
@@ -871,6 +889,7 @@ class HookQuerySetMixin:
|
|
|
871
889
|
# Handle auto_now fields like Django's update_or_create does
|
|
872
890
|
fields_set = set(fields)
|
|
873
891
|
pk_fields = model_cls._meta.pk_fields
|
|
892
|
+
pk_field_names = [f.name for f in pk_fields]
|
|
874
893
|
auto_now_fields = []
|
|
875
894
|
custom_update_fields = [] # Fields that need pre_save() called on update
|
|
876
895
|
logger.debug(f"Checking for auto_now and custom update fields in {model_cls.__name__}")
|
|
@@ -880,7 +899,7 @@ class HookQuerySetMixin:
|
|
|
880
899
|
if hasattr(field, "auto_now") and field.auto_now:
|
|
881
900
|
logger.debug(f"Found auto_now field: {field.name}")
|
|
882
901
|
print(f"DEBUG: Found auto_now field: {field.name}")
|
|
883
|
-
if field.name not in fields_set and field.name not in
|
|
902
|
+
if field.name not in fields_set and field.name not in pk_field_names:
|
|
884
903
|
fields_set.add(field.name)
|
|
885
904
|
if field.name != field.attname:
|
|
886
905
|
fields_set.add(field.attname)
|
|
@@ -895,7 +914,7 @@ class HookQuerySetMixin:
|
|
|
895
914
|
# Check for custom fields that might need pre_save() on update (like CurrentUserField)
|
|
896
915
|
elif hasattr(field, 'pre_save'):
|
|
897
916
|
# Only call pre_save on fields that aren't already being updated
|
|
898
|
-
if field.name not in fields_set and field.name not in
|
|
917
|
+
if field.name not in fields_set and field.name not in pk_field_names:
|
|
899
918
|
custom_update_fields.append(field)
|
|
900
919
|
logger.debug(f"Found custom field with pre_save: {field.name}")
|
|
901
920
|
print(f"DEBUG: Found custom field with pre_save: {field.name}")
|
|
@@ -927,8 +946,8 @@ class HookQuerySetMixin:
|
|
|
927
946
|
# Only update the field if pre_save returned a new value
|
|
928
947
|
if new_value is not None:
|
|
929
948
|
setattr(obj, field.name, new_value)
|
|
930
|
-
# Add this field to the update fields if it's not already there
|
|
931
|
-
if field.name not in fields_set:
|
|
949
|
+
# Add this field to the update fields if it's not already there and not a primary key
|
|
950
|
+
if field.name not in fields_set and field.name not in pk_field_names:
|
|
932
951
|
fields_set.add(field.name)
|
|
933
952
|
fields.append(field.name)
|
|
934
953
|
logger.debug(f"Custom field {field.name} updated via pre_save() for object {obj.pk}")
|
|
@@ -1291,13 +1310,14 @@ class HookQuerySetMixin:
|
|
|
1291
1310
|
|
|
1292
1311
|
return child_obj
|
|
1293
1312
|
|
|
1294
|
-
def _mti_bulk_update(self, objs, fields, **kwargs):
|
|
1313
|
+
def _mti_bulk_update(self, objs, fields, field_groups=None, inheritance_chain=None, **kwargs):
|
|
1295
1314
|
"""
|
|
1296
1315
|
Custom bulk update implementation for MTI models.
|
|
1297
1316
|
Updates each table in the inheritance chain efficiently using Django's batch_size.
|
|
1298
1317
|
"""
|
|
1299
1318
|
model_cls = self.model
|
|
1300
|
-
inheritance_chain
|
|
1319
|
+
if inheritance_chain is None:
|
|
1320
|
+
inheritance_chain = self._get_inheritance_chain()
|
|
1301
1321
|
|
|
1302
1322
|
# Remove custom hook kwargs before passing to Django internals
|
|
1303
1323
|
django_kwargs = {
|
|
@@ -1341,17 +1361,18 @@ class HookQuerySetMixin:
|
|
|
1341
1361
|
# Add custom fields that were updated to the fields list
|
|
1342
1362
|
all_fields = list(fields) + list(auto_now_fields) + custom_update_fields
|
|
1343
1363
|
|
|
1344
|
-
# Group fields by model in the inheritance chain
|
|
1345
|
-
field_groups
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
if
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1364
|
+
# Group fields by model in the inheritance chain (if not provided)
|
|
1365
|
+
if field_groups is None:
|
|
1366
|
+
field_groups = {}
|
|
1367
|
+
for field_name in all_fields:
|
|
1368
|
+
field = model_cls._meta.get_field(field_name)
|
|
1369
|
+
# Find which model in the inheritance chain this field belongs to
|
|
1370
|
+
for model in inheritance_chain:
|
|
1371
|
+
if field in model._meta.local_fields:
|
|
1372
|
+
if model not in field_groups:
|
|
1373
|
+
field_groups[model] = []
|
|
1374
|
+
field_groups[model].append(field_name)
|
|
1375
|
+
break
|
|
1355
1376
|
|
|
1356
1377
|
# Process in batches
|
|
1357
1378
|
batch_size = django_kwargs.get("batch_size") or len(objs)
|
|
@@ -9,9 +9,9 @@ django_bulk_hooks/handler.py,sha256=Bx-W6yyiciKMyy-BRxUt3CmRPCrX9_LhQgU-5LaJTjg,
|
|
|
9
9
|
django_bulk_hooks/manager.py,sha256=nfWiwU5-yAoxdnQsUMohxtyCpkV0MBv6X3wmipr9eQY,3697
|
|
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=-Fuo6K4f4Mhwq9lUkAE4rpaVUHAvGASgktYJRjg-wXY,77120
|
|
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.262.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
15
|
+
django_bulk_hooks-0.1.262.dist-info/METADATA,sha256=dh0WsqOdiy_L8HTSwFduekbyeb-tmNHVo2QqzWv9ZkU,9061
|
|
16
|
+
django_bulk_hooks-0.1.262.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
17
|
+
django_bulk_hooks-0.1.262.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|