django-bulk-hooks 0.1.233__py3-none-any.whl → 0.1.235__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.
- django_bulk_hooks/queryset.py +33 -50
- {django_bulk_hooks-0.1.233.dist-info → django_bulk_hooks-0.1.235.dist-info}/METADATA +1 -1
- {django_bulk_hooks-0.1.233.dist-info → django_bulk_hooks-0.1.235.dist-info}/RECORD +5 -5
- {django_bulk_hooks-0.1.233.dist-info → django_bulk_hooks-0.1.235.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.1.233.dist-info → django_bulk_hooks-0.1.235.dist-info}/WHEEL +0 -0
django_bulk_hooks/queryset.py
CHANGED
|
@@ -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
|
-
#
|
|
69
|
-
|
|
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
|
|
73
|
+
# Apply field updates to instances
|
|
72
74
|
for obj in instances:
|
|
73
|
-
for field, value in
|
|
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
|
-
#
|
|
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)
|
|
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)
|
|
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)
|
|
@@ -9,9 +9,9 @@ django_bulk_hooks/handler.py,sha256=xZt8iNdYF-ACz-MnKMY0co6scWINU5V5wC1lyDn844k,
|
|
|
9
9
|
django_bulk_hooks/manager.py,sha256=nfWiwU5-yAoxdnQsUMohxtyCpkV0MBv6X3wmipr9eQY,3697
|
|
10
10
|
django_bulk_hooks/models.py,sha256=exnXYVKEVbYAXhChCP8VdWTnKCnm9DiTcokEIBee1I0,4350
|
|
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=YSDCMAf24YLeLJrRKCDYwbZInP2mK_Tcuw3EHLDkv_w,32605
|
|
13
13
|
django_bulk_hooks/registry.py,sha256=8UuhniiH5ChSeOKV1UUbqTEiIu25bZXvcHmkaRbxmME,1131
|
|
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.235.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
15
|
+
django_bulk_hooks-0.1.235.dist-info/METADATA,sha256=_AvaKqJPklOzTxh8VnSpQCG8z8B2sPoltcq3phrz56A,9049
|
|
16
|
+
django_bulk_hooks-0.1.235.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
17
|
+
django_bulk_hooks-0.1.235.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|