django-bulk-hooks 0.1.277__tar.gz → 0.1.279__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.

Files changed (17) hide show
  1. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/PKG-INFO +1 -1
  2. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/django_bulk_hooks/queryset.py +90 -27
  3. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/pyproject.toml +1 -1
  4. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/LICENSE +0 -0
  5. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/README.md +0 -0
  6. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/django_bulk_hooks/__init__.py +0 -0
  7. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/django_bulk_hooks/conditions.py +0 -0
  8. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/django_bulk_hooks/constants.py +0 -0
  9. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/django_bulk_hooks/context.py +0 -0
  10. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/django_bulk_hooks/decorators.py +0 -0
  11. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/django_bulk_hooks/engine.py +0 -0
  12. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/django_bulk_hooks/enums.py +0 -0
  13. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/django_bulk_hooks/handler.py +0 -0
  14. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/django_bulk_hooks/manager.py +0 -0
  15. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/django_bulk_hooks/models.py +0 -0
  16. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/django_bulk_hooks/priority.py +0 -0
  17. {django_bulk_hooks-0.1.277 → django_bulk_hooks-0.1.279}/django_bulk_hooks/registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-bulk-hooks
3
- Version: 0.1.277
3
+ Version: 0.1.279
4
4
  Summary: Hook-style hooks for Django bulk operations like bulk_create and bulk_update.
5
5
  Home-page: https://github.com/AugendLimited/django-bulk-hooks
6
6
  License: MIT
@@ -812,6 +812,16 @@ class HookQuerySetMixin:
812
812
  "update_fields": update_fields,
813
813
  "unique_fields": unique_fields,
814
814
  }
815
+
816
+ # If we have classified records from upsert logic, pass them to MTI method
817
+ if (
818
+ update_conflicts
819
+ and unique_fields
820
+ and hasattr(ctx, "upsert_existing_records")
821
+ ):
822
+ mti_kwargs["existing_records"] = ctx.upsert_existing_records
823
+ mti_kwargs["new_records"] = ctx.upsert_new_records
824
+
815
825
  # Remove custom hook kwargs if present in self.bulk_create signature
816
826
  result = self._mti_bulk_create(
817
827
  objs,
@@ -1626,6 +1636,10 @@ class HookQuerySetMixin:
1626
1636
  then single bulk insert into childmost table.
1627
1637
  Sets auto_now_add/auto_now fields for each model in the chain.
1628
1638
  """
1639
+ # Extract classified records if available (for upsert operations)
1640
+ existing_records = kwargs.pop("existing_records", [])
1641
+ new_records = kwargs.pop("new_records", [])
1642
+
1629
1643
  # Remove custom hook kwargs before passing to Django internals
1630
1644
  django_kwargs = {
1631
1645
  k: v
@@ -1647,12 +1661,23 @@ class HookQuerySetMixin:
1647
1661
  for i in range(0, len(objs), batch_size):
1648
1662
  batch = objs[i : i + batch_size]
1649
1663
  batch_result = self._process_mti_bulk_create_batch(
1650
- batch, inheritance_chain, **django_kwargs
1664
+ batch,
1665
+ inheritance_chain,
1666
+ existing_records,
1667
+ new_records,
1668
+ **django_kwargs,
1651
1669
  )
1652
1670
  created_objects.extend(batch_result)
1653
1671
  return created_objects
1654
1672
 
1655
- def _process_mti_bulk_create_batch(self, batch, inheritance_chain, **kwargs):
1673
+ def _process_mti_bulk_create_batch(
1674
+ self,
1675
+ batch,
1676
+ inheritance_chain,
1677
+ existing_records=None,
1678
+ new_records=None,
1679
+ **kwargs,
1680
+ ):
1656
1681
  """
1657
1682
  Process a single batch of objects through the inheritance chain.
1658
1683
  Implements Django's suggested workaround #2: O(n) normal inserts into parent
@@ -1667,41 +1692,79 @@ class HookQuerySetMixin:
1667
1692
  bypass_hooks = kwargs.get("bypass_hooks", False)
1668
1693
  bypass_validation = kwargs.get("bypass_validation", False)
1669
1694
 
1695
+ # Create a list for lookup (since model instances without PKs are not hashable)
1696
+ existing_records_list = existing_records if existing_records else []
1697
+
1670
1698
  for obj in batch:
1671
1699
  parent_instances = {}
1672
1700
  current_parent = None
1701
+ is_existing_record = obj in existing_records_list
1702
+
1673
1703
  for model_class in inheritance_chain[:-1]:
1674
1704
  parent_obj = self._create_parent_instance(
1675
1705
  obj, model_class, current_parent
1676
1706
  )
1677
1707
 
1678
- # Fire parent hooks if not bypassed
1679
- if not bypass_hooks:
1680
- ctx = HookContext(model_class)
1681
- if not bypass_validation:
1682
- engine.run(model_class, VALIDATE_CREATE, [parent_obj], ctx=ctx)
1683
- engine.run(model_class, BEFORE_CREATE, [parent_obj], ctx=ctx)
1684
-
1685
- # Use Django's base manager to create the object and get PKs back
1686
- # This bypasses hooks and the MTI exception
1687
- field_values = {
1688
- field.name: getattr(parent_obj, field.name)
1689
- for field in model_class._meta.local_fields
1690
- if hasattr(parent_obj, field.name)
1691
- and getattr(parent_obj, field.name) is not None
1692
- }
1693
- created_obj = model_class._base_manager.using(self.db).create(
1694
- **field_values
1695
- )
1708
+ if is_existing_record:
1709
+ # For existing records, we need to update the parent object instead of creating
1710
+ # The parent_obj should already have the correct PK from the database lookup
1711
+ # Fire parent hooks for updates
1712
+ if not bypass_hooks:
1713
+ ctx = HookContext(model_class)
1714
+ if not bypass_validation:
1715
+ engine.run(
1716
+ model_class, VALIDATE_UPDATE, [parent_obj], ctx=ctx
1717
+ )
1718
+ engine.run(model_class, BEFORE_UPDATE, [parent_obj], ctx=ctx)
1719
+
1720
+ # Update the existing parent object
1721
+ # Filter update_fields to only include fields that exist in the parent model
1722
+ parent_update_fields = kwargs.get("update_fields")
1723
+ if parent_update_fields:
1724
+ # Only include fields that exist in the parent model
1725
+ parent_model_fields = {field.name for field in model_class._meta.local_fields}
1726
+ filtered_update_fields = [
1727
+ field for field in parent_update_fields
1728
+ if field in parent_model_fields
1729
+ ]
1730
+ parent_obj.save(update_fields=filtered_update_fields)
1731
+ else:
1732
+ parent_obj.save()
1733
+
1734
+ # Fire AFTER_UPDATE hooks for parent
1735
+ if not bypass_hooks:
1736
+ engine.run(model_class, AFTER_UPDATE, [parent_obj], ctx=ctx)
1737
+ else:
1738
+ # For new records, create the parent object as before
1739
+ # Fire parent hooks if not bypassed
1740
+ if not bypass_hooks:
1741
+ ctx = HookContext(model_class)
1742
+ if not bypass_validation:
1743
+ engine.run(
1744
+ model_class, VALIDATE_CREATE, [parent_obj], ctx=ctx
1745
+ )
1746
+ engine.run(model_class, BEFORE_CREATE, [parent_obj], ctx=ctx)
1747
+
1748
+ # Use Django's base manager to create the object and get PKs back
1749
+ # This bypasses hooks and the MTI exception
1750
+ field_values = {
1751
+ field.name: getattr(parent_obj, field.name)
1752
+ for field in model_class._meta.local_fields
1753
+ if hasattr(parent_obj, field.name)
1754
+ and getattr(parent_obj, field.name) is not None
1755
+ }
1756
+ created_obj = model_class._base_manager.using(self.db).create(
1757
+ **field_values
1758
+ )
1696
1759
 
1697
- # Update the parent_obj with the created object's PK
1698
- parent_obj.pk = created_obj.pk
1699
- parent_obj._state.adding = False
1700
- parent_obj._state.db = self.db
1760
+ # Update the parent_obj with the created object's PK
1761
+ parent_obj.pk = created_obj.pk
1762
+ parent_obj._state.adding = False
1763
+ parent_obj._state.db = self.db
1701
1764
 
1702
- # Fire AFTER_CREATE hooks for parent
1703
- if not bypass_hooks:
1704
- engine.run(model_class, AFTER_CREATE, [parent_obj], ctx=ctx)
1765
+ # Fire AFTER_CREATE hooks for parent
1766
+ if not bypass_hooks:
1767
+ engine.run(model_class, AFTER_CREATE, [parent_obj], ctx=ctx)
1705
1768
 
1706
1769
  parent_instances[model_class] = parent_obj
1707
1770
  current_parent = parent_obj
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-bulk-hooks"
3
- version = "0.1.277"
3
+ version = "0.1.279"
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"