django-bulk-hooks 0.1.233__tar.gz → 0.1.235__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.233 → django_bulk_hooks-0.1.235}/PKG-INFO +1 -1
  2. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/django_bulk_hooks/queryset.py +33 -50
  3. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/pyproject.toml +1 -1
  4. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/LICENSE +0 -0
  5. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/README.md +0 -0
  6. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/django_bulk_hooks/__init__.py +0 -0
  7. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/django_bulk_hooks/conditions.py +0 -0
  8. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/django_bulk_hooks/constants.py +0 -0
  9. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/django_bulk_hooks/context.py +0 -0
  10. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/django_bulk_hooks/decorators.py +0 -0
  11. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/django_bulk_hooks/engine.py +0 -0
  12. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/django_bulk_hooks/enums.py +0 -0
  13. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/django_bulk_hooks/handler.py +0 -0
  14. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/django_bulk_hooks/manager.py +0 -0
  15. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/django_bulk_hooks/models.py +0 -0
  16. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/django_bulk_hooks/priority.py +0 -0
  17. {django_bulk_hooks-0.1.233 → django_bulk_hooks-0.1.235}/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.233
3
+ Version: 0.1.235
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
@@ -1,7 +1,6 @@
1
1
  import logging
2
-
3
2
  from django.db import models, transaction
4
- from django.db.models import AutoField, Case, Value, When
3
+ from django.db.models import AutoField, Case, Field, Value, When
5
4
 
6
5
  from django_bulk_hooks import engine
7
6
 
@@ -65,19 +64,21 @@ class HookQuerySetMixin:
65
64
  }
66
65
  originals = [original_map.get(obj.pk) for obj in instances]
67
66
 
68
- # Resolve subqueries to actual values before applying to instances
69
- resolved_kwargs = self._resolve_subquery_values(kwargs)
67
+ # Check if any of the update values are Subquery objects
68
+ has_subquery = any(
69
+ hasattr(value, "query") and hasattr(value, "resolve_expression")
70
+ for value in kwargs.values()
71
+ )
70
72
 
71
- # Apply resolved field updates to instances
73
+ # Apply field updates to instances
72
74
  for obj in instances:
73
- for field, value in resolved_kwargs.items():
75
+ for field, value in kwargs.items():
74
76
  setattr(obj, field, value)
75
77
 
76
78
  # Check if we're in a bulk operation context to prevent double hook execution
77
79
  from django_bulk_hooks.context import get_bypass_hooks
78
-
79
80
  current_bypass_hooks = get_bypass_hooks()
80
-
81
+
81
82
  # If we're in a bulk operation context, skip hooks to prevent double execution
82
83
  if current_bypass_hooks:
83
84
  logger.debug("update: skipping hooks (bulk context)")
@@ -92,10 +93,28 @@ class HookQuerySetMixin:
92
93
 
93
94
  # Use Django's built-in update logic directly
94
95
  # Call the base QuerySet implementation to avoid recursion
95
- # Use original kwargs so Django can handle subqueries at database level
96
96
  update_count = super().update(**kwargs)
97
97
 
98
- # Since we resolved subqueries upfront, we don't need the post-refresh logic
98
+ # If we used Subquery objects, refresh the instances to get computed values
99
+ if has_subquery and instances:
100
+ # Simple refresh of model fields without fetching related objects
101
+ # Subquery updates only affect the model's own fields, not relationships
102
+ refreshed_instances = {
103
+ obj.pk: obj for obj in model_cls._base_manager.filter(pk__in=pks)
104
+ }
105
+
106
+ # Bulk update all instances in memory
107
+ for instance in instances:
108
+ if instance.pk in refreshed_instances:
109
+ refreshed_instance = refreshed_instances[instance.pk]
110
+ # Update all fields except primary key
111
+ for field in model_cls._meta.fields:
112
+ if field.name != "id":
113
+ setattr(
114
+ instance,
115
+ field.name,
116
+ getattr(refreshed_instance, field.name),
117
+ )
99
118
 
100
119
  # Run AFTER_UPDATE hooks only for standalone updates
101
120
  if not current_bypass_hooks:
@@ -106,38 +125,6 @@ class HookQuerySetMixin:
106
125
 
107
126
  return update_count
108
127
 
109
- def _resolve_subquery_values(self, kwargs):
110
- """
111
- Resolve Subquery objects to their actual values by evaluating them
112
- against the database. This ensures hooks receive resolved values
113
- instead of raw Subquery objects.
114
- """
115
- resolved_kwargs = {}
116
- for field, value in kwargs.items():
117
- if hasattr(value, "query") and hasattr(value, "resolve_expression"):
118
- # This is a subquery - we need to resolve it
119
- try:
120
- # Create a temporary queryset to evaluate the subquery
121
- temp_qs = self.model._default_manager.all()
122
- temp_qs = temp_qs.annotate(_temp_field=value)
123
- temp_qs = temp_qs.values("_temp_field")
124
-
125
- # Get the resolved value (assuming single result for update context)
126
- resolved_value = temp_qs.first()["_temp_field"]
127
- resolved_kwargs[field] = resolved_value
128
- except Exception:
129
- # If resolution fails, use the original subquery
130
- # Django's update() will handle it at the database level
131
- logger.warning(
132
- f"Failed to resolve subquery for field {field}, using original"
133
- )
134
- resolved_kwargs[field] = value
135
- else:
136
- # Not a subquery, use as-is
137
- resolved_kwargs[field] = value
138
-
139
- return resolved_kwargs
140
-
141
128
  @transaction.atomic
142
129
  def bulk_create(
143
130
  self,
@@ -193,12 +180,12 @@ class HookQuerySetMixin:
193
180
 
194
181
  # Fire hooks before DB ops
195
182
  if not bypass_hooks:
196
- ctx = HookContext(model_cls, bypass_hooks=False) # Pass bypass_hooks
183
+ ctx = HookContext(model_cls, bypass_hooks=False) # Pass bypass_hooks
197
184
  if not bypass_validation:
198
185
  engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
199
186
  engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
200
187
  else:
201
- ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
188
+ ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
202
189
  logger.debug("bulk_create bypassed hooks")
203
190
 
204
191
  # For MTI models, we need to handle them specially
@@ -254,9 +241,7 @@ class HookQuerySetMixin:
254
241
  f"bulk_update expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
255
242
  )
256
243
 
257
- logger.debug(
258
- f"bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}"
259
- )
244
+ logger.debug(f"bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}")
260
245
 
261
246
  # Check for MTI
262
247
  is_mti = False
@@ -272,9 +257,7 @@ class HookQuerySetMixin:
272
257
  else:
273
258
  logger.debug("bulk_update: hooks bypassed")
274
259
  ctx = HookContext(model_cls, bypass_hooks=True)
275
- originals = [None] * len(
276
- objs
277
- ) # Ensure originals is defined for after_update call
260
+ originals = [None] * len(objs) # Ensure originals is defined for after_update call
278
261
 
279
262
  # Handle auto_now fields like Django's update_or_create does
280
263
  fields_set = set(fields)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-bulk-hooks"
3
- version = "0.1.233"
3
+ version = "0.1.235"
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"