django-bulk-hooks 0.1.251__tar.gz → 0.1.253__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.251 → django_bulk_hooks-0.1.253}/PKG-INFO +1 -1
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/django_bulk_hooks/queryset.py +190 -5
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/pyproject.toml +1 -1
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/LICENSE +0 -0
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/README.md +0 -0
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/django_bulk_hooks/__init__.py +0 -0
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/django_bulk_hooks/conditions.py +0 -0
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/django_bulk_hooks/constants.py +0 -0
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/django_bulk_hooks/context.py +0 -0
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/django_bulk_hooks/decorators.py +0 -0
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/django_bulk_hooks/engine.py +0 -0
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/django_bulk_hooks/enums.py +0 -0
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/django_bulk_hooks/handler.py +0 -0
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/django_bulk_hooks/manager.py +0 -0
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/django_bulk_hooks/models.py +0 -0
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/django_bulk_hooks/priority.py +0 -0
- {django_bulk_hooks-0.1.251 → django_bulk_hooks-0.1.253}/django_bulk_hooks/registry.py +0 -0
|
@@ -520,9 +520,133 @@ class HookQuerySetMixin:
|
|
|
520
520
|
# Fire hooks before DB ops
|
|
521
521
|
if not bypass_hooks:
|
|
522
522
|
ctx = HookContext(model_cls, bypass_hooks=False) # Pass bypass_hooks
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
523
|
+
|
|
524
|
+
if update_conflicts and unique_fields:
|
|
525
|
+
# For upsert operations, we need to determine which records will be created vs updated
|
|
526
|
+
# Check which records already exist in the database based on unique fields
|
|
527
|
+
existing_records = []
|
|
528
|
+
new_records = []
|
|
529
|
+
|
|
530
|
+
# Build a filter to check which records already exist
|
|
531
|
+
unique_values = []
|
|
532
|
+
for obj in objs:
|
|
533
|
+
unique_value = {}
|
|
534
|
+
for field_name in unique_fields:
|
|
535
|
+
if hasattr(obj, field_name):
|
|
536
|
+
unique_value[field_name] = getattr(obj, field_name)
|
|
537
|
+
if unique_value:
|
|
538
|
+
unique_values.append(unique_value)
|
|
539
|
+
|
|
540
|
+
if unique_values:
|
|
541
|
+
# Query the database to see which records already exist
|
|
542
|
+
from django.db.models import Q
|
|
543
|
+
existing_filters = Q()
|
|
544
|
+
for unique_value in unique_values:
|
|
545
|
+
filter_kwargs = {}
|
|
546
|
+
for field_name, value in unique_value.items():
|
|
547
|
+
filter_kwargs[field_name] = value
|
|
548
|
+
existing_filters |= Q(**filter_kwargs)
|
|
549
|
+
|
|
550
|
+
existing_pks = set(
|
|
551
|
+
model_cls.objects.filter(existing_filters).values_list('pk', flat=True)
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
# Separate records based on whether they already exist
|
|
555
|
+
for obj in objs:
|
|
556
|
+
obj_unique_value = {}
|
|
557
|
+
for field_name in unique_fields:
|
|
558
|
+
if hasattr(obj, field_name):
|
|
559
|
+
obj_unique_value[field_name] = getattr(obj, field_name)
|
|
560
|
+
|
|
561
|
+
# Check if this record already exists
|
|
562
|
+
if obj_unique_value:
|
|
563
|
+
existing_q = Q()
|
|
564
|
+
for field_name, value in obj_unique_value.items():
|
|
565
|
+
existing_q &= Q(**{field_name: value})
|
|
566
|
+
|
|
567
|
+
if model_cls.objects.filter(existing_q).exists():
|
|
568
|
+
existing_records.append(obj)
|
|
569
|
+
else:
|
|
570
|
+
new_records.append(obj)
|
|
571
|
+
else:
|
|
572
|
+
# If we can't determine uniqueness, treat as new
|
|
573
|
+
new_records.append(obj)
|
|
574
|
+
else:
|
|
575
|
+
# If no unique fields, treat all as new
|
|
576
|
+
new_records = objs
|
|
577
|
+
|
|
578
|
+
# Handle auto_now fields intelligently for upsert operations
|
|
579
|
+
# Only set auto_now fields on records that will actually be created
|
|
580
|
+
for obj in new_records:
|
|
581
|
+
for field in model_cls._meta.local_fields:
|
|
582
|
+
if hasattr(field, "auto_now") and field.auto_now:
|
|
583
|
+
field.pre_save(obj, add=True)
|
|
584
|
+
elif hasattr(field, "auto_now_add") and field.auto_now_add:
|
|
585
|
+
if getattr(obj, field.name) is None:
|
|
586
|
+
field.pre_save(obj, add=True)
|
|
587
|
+
|
|
588
|
+
# For existing records, preserve their original auto_now values
|
|
589
|
+
# We'll need to fetch them from the database to preserve the timestamps
|
|
590
|
+
if existing_records:
|
|
591
|
+
# Get the unique field values for existing records
|
|
592
|
+
existing_unique_values = []
|
|
593
|
+
for obj in existing_records:
|
|
594
|
+
unique_value = {}
|
|
595
|
+
for field_name in unique_fields:
|
|
596
|
+
if hasattr(obj, field_name):
|
|
597
|
+
unique_value[field_name] = getattr(obj, field_name)
|
|
598
|
+
if unique_value:
|
|
599
|
+
existing_unique_values.append(unique_value)
|
|
600
|
+
|
|
601
|
+
if existing_unique_values:
|
|
602
|
+
# Build filter to fetch existing records
|
|
603
|
+
existing_filters = Q()
|
|
604
|
+
for unique_value in existing_unique_values:
|
|
605
|
+
filter_kwargs = {}
|
|
606
|
+
for field_name, value in unique_value.items():
|
|
607
|
+
filter_kwargs[field_name] = value
|
|
608
|
+
existing_filters |= Q(**filter_kwargs)
|
|
609
|
+
|
|
610
|
+
# Fetch existing records to preserve their auto_now values
|
|
611
|
+
existing_db_records = model_cls.objects.filter(existing_filters)
|
|
612
|
+
existing_db_map = {}
|
|
613
|
+
for db_record in existing_db_records:
|
|
614
|
+
key = tuple(getattr(db_record, field) for field in unique_fields)
|
|
615
|
+
existing_db_map[key] = db_record
|
|
616
|
+
|
|
617
|
+
# Preserve auto_now field values for existing records
|
|
618
|
+
for obj in existing_records:
|
|
619
|
+
key = tuple(getattr(obj, field) for field in unique_fields)
|
|
620
|
+
if key in existing_db_map:
|
|
621
|
+
db_record = existing_db_map[key]
|
|
622
|
+
for field in model_cls._meta.local_fields:
|
|
623
|
+
if hasattr(field, "auto_now") and field.auto_now:
|
|
624
|
+
# Preserve the original updated_at timestamp
|
|
625
|
+
setattr(obj, field.name, getattr(db_record, field.name))
|
|
626
|
+
|
|
627
|
+
# Run validation hooks on all records
|
|
628
|
+
if not bypass_validation:
|
|
629
|
+
engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
|
|
630
|
+
|
|
631
|
+
# Run appropriate BEFORE hooks based on what will happen
|
|
632
|
+
if new_records:
|
|
633
|
+
engine.run(model_cls, BEFORE_CREATE, new_records, ctx=ctx)
|
|
634
|
+
if existing_records:
|
|
635
|
+
engine.run(model_cls, BEFORE_UPDATE, existing_records, ctx=ctx)
|
|
636
|
+
else:
|
|
637
|
+
# For regular create operations, run create hooks before DB ops
|
|
638
|
+
# Handle auto_now fields normally for new records
|
|
639
|
+
for obj in objs:
|
|
640
|
+
for field in model_cls._meta.local_fields:
|
|
641
|
+
if hasattr(field, "auto_now") and field.auto_now:
|
|
642
|
+
field.pre_save(obj, add=True)
|
|
643
|
+
elif hasattr(field, "auto_now_add") and field.auto_now_add:
|
|
644
|
+
if getattr(obj, field.name) is None:
|
|
645
|
+
field.pre_save(obj, add=True)
|
|
646
|
+
|
|
647
|
+
if not bypass_validation:
|
|
648
|
+
engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
|
|
649
|
+
engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
|
|
526
650
|
else:
|
|
527
651
|
ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
|
|
528
652
|
logger.debug("bulk_create bypassed hooks")
|
|
@@ -557,9 +681,70 @@ class HookQuerySetMixin:
|
|
|
557
681
|
unique_fields=unique_fields,
|
|
558
682
|
)
|
|
559
683
|
|
|
560
|
-
# Fire
|
|
684
|
+
# Fire AFTER hooks
|
|
561
685
|
if not bypass_hooks:
|
|
562
|
-
|
|
686
|
+
if update_conflicts and unique_fields:
|
|
687
|
+
# For upsert operations, we need to determine which records were actually created vs updated
|
|
688
|
+
# Use the same logic as before to separate records
|
|
689
|
+
existing_records = []
|
|
690
|
+
new_records = []
|
|
691
|
+
|
|
692
|
+
# Build a filter to check which records already exist
|
|
693
|
+
unique_values = []
|
|
694
|
+
for obj in objs:
|
|
695
|
+
unique_value = {}
|
|
696
|
+
for field_name in unique_fields:
|
|
697
|
+
if hasattr(obj, field_name):
|
|
698
|
+
unique_value[field_name] = getattr(obj, field_name)
|
|
699
|
+
if unique_value:
|
|
700
|
+
unique_values.append(unique_value)
|
|
701
|
+
|
|
702
|
+
if unique_values:
|
|
703
|
+
# Query the database to see which records already exist
|
|
704
|
+
from django.db.models import Q
|
|
705
|
+
existing_filters = Q()
|
|
706
|
+
for unique_value in unique_values:
|
|
707
|
+
filter_kwargs = {}
|
|
708
|
+
for field_name, value in unique_value.items():
|
|
709
|
+
filter_kwargs[field_name] = value
|
|
710
|
+
existing_filters |= Q(**filter_kwargs)
|
|
711
|
+
|
|
712
|
+
existing_pks = set(
|
|
713
|
+
model_cls.objects.filter(existing_filters).values_list('pk', flat=True)
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
# Separate records based on whether they already exist
|
|
717
|
+
for obj in objs:
|
|
718
|
+
obj_unique_value = {}
|
|
719
|
+
for field_name in unique_fields:
|
|
720
|
+
if hasattr(obj, field_name):
|
|
721
|
+
obj_unique_value[field_name] = getattr(obj, field_name)
|
|
722
|
+
|
|
723
|
+
# Check if this record already exists
|
|
724
|
+
if obj_unique_value:
|
|
725
|
+
existing_q = Q()
|
|
726
|
+
for field_name, value in obj_unique_value.items():
|
|
727
|
+
existing_q &= Q(**{field_name: value})
|
|
728
|
+
|
|
729
|
+
if model_cls.objects.filter(existing_q).exists():
|
|
730
|
+
existing_records.append(obj)
|
|
731
|
+
else:
|
|
732
|
+
new_records.append(obj)
|
|
733
|
+
else:
|
|
734
|
+
# If we can't determine uniqueness, treat as new
|
|
735
|
+
new_records.append(obj)
|
|
736
|
+
else:
|
|
737
|
+
# If no unique fields, treat all as new
|
|
738
|
+
new_records = objs
|
|
739
|
+
|
|
740
|
+
# Run appropriate AFTER hooks based on what actually happened
|
|
741
|
+
if new_records:
|
|
742
|
+
engine.run(model_cls, AFTER_CREATE, new_records, ctx=ctx)
|
|
743
|
+
if existing_records:
|
|
744
|
+
engine.run(model_cls, AFTER_UPDATE, existing_records, ctx=ctx)
|
|
745
|
+
else:
|
|
746
|
+
# For regular create operations, run create hooks after DB ops
|
|
747
|
+
engine.run(model_cls, AFTER_CREATE, objs, ctx=ctx)
|
|
563
748
|
|
|
564
749
|
return result
|
|
565
750
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "django-bulk-hooks"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.253"
|
|
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
|