django-bulk-hooks 0.1.232__py3-none-any.whl → 0.1.233__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 +50 -33
- {django_bulk_hooks-0.1.232.dist-info → django_bulk_hooks-0.1.233.dist-info}/METADATA +3 -3
- {django_bulk_hooks-0.1.232.dist-info → django_bulk_hooks-0.1.233.dist-info}/RECORD +5 -5
- {django_bulk_hooks-0.1.232.dist-info → django_bulk_hooks-0.1.233.dist-info}/WHEEL +1 -1
- {django_bulk_hooks-0.1.232.dist-info → django_bulk_hooks-0.1.233.dist-info}/LICENSE +0 -0
django_bulk_hooks/queryset.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
|
|
2
3
|
from django.db import models, transaction
|
|
3
|
-
from django.db.models import AutoField, Case,
|
|
4
|
+
from django.db.models import AutoField, Case, Value, When
|
|
4
5
|
|
|
5
6
|
from django_bulk_hooks import engine
|
|
6
7
|
|
|
@@ -64,21 +65,19 @@ class HookQuerySetMixin:
|
|
|
64
65
|
}
|
|
65
66
|
originals = [original_map.get(obj.pk) for obj in instances]
|
|
66
67
|
|
|
67
|
-
#
|
|
68
|
-
|
|
69
|
-
hasattr(value, "query") and hasattr(value, "resolve_expression")
|
|
70
|
-
for value in kwargs.values()
|
|
71
|
-
)
|
|
68
|
+
# Resolve subqueries to actual values before applying to instances
|
|
69
|
+
resolved_kwargs = self._resolve_subquery_values(kwargs)
|
|
72
70
|
|
|
73
|
-
# Apply field updates to instances
|
|
71
|
+
# Apply resolved field updates to instances
|
|
74
72
|
for obj in instances:
|
|
75
|
-
for field, value in
|
|
73
|
+
for field, value in resolved_kwargs.items():
|
|
76
74
|
setattr(obj, field, value)
|
|
77
75
|
|
|
78
76
|
# Check if we're in a bulk operation context to prevent double hook execution
|
|
79
77
|
from django_bulk_hooks.context import get_bypass_hooks
|
|
78
|
+
|
|
80
79
|
current_bypass_hooks = get_bypass_hooks()
|
|
81
|
-
|
|
80
|
+
|
|
82
81
|
# If we're in a bulk operation context, skip hooks to prevent double execution
|
|
83
82
|
if current_bypass_hooks:
|
|
84
83
|
logger.debug("update: skipping hooks (bulk context)")
|
|
@@ -93,28 +92,10 @@ class HookQuerySetMixin:
|
|
|
93
92
|
|
|
94
93
|
# Use Django's built-in update logic directly
|
|
95
94
|
# 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
|
-
#
|
|
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
|
-
)
|
|
98
|
+
# Since we resolved subqueries upfront, we don't need the post-refresh logic
|
|
118
99
|
|
|
119
100
|
# Run AFTER_UPDATE hooks only for standalone updates
|
|
120
101
|
if not current_bypass_hooks:
|
|
@@ -125,6 +106,38 @@ class HookQuerySetMixin:
|
|
|
125
106
|
|
|
126
107
|
return update_count
|
|
127
108
|
|
|
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
|
+
|
|
128
141
|
@transaction.atomic
|
|
129
142
|
def bulk_create(
|
|
130
143
|
self,
|
|
@@ -180,12 +193,12 @@ class HookQuerySetMixin:
|
|
|
180
193
|
|
|
181
194
|
# Fire hooks before DB ops
|
|
182
195
|
if not bypass_hooks:
|
|
183
|
-
ctx = HookContext(model_cls, bypass_hooks=False)
|
|
196
|
+
ctx = HookContext(model_cls, bypass_hooks=False) # Pass bypass_hooks
|
|
184
197
|
if not bypass_validation:
|
|
185
198
|
engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
|
|
186
199
|
engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
|
|
187
200
|
else:
|
|
188
|
-
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
201
|
+
ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
|
|
189
202
|
logger.debug("bulk_create bypassed hooks")
|
|
190
203
|
|
|
191
204
|
# For MTI models, we need to handle them specially
|
|
@@ -241,7 +254,9 @@ class HookQuerySetMixin:
|
|
|
241
254
|
f"bulk_update expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
|
|
242
255
|
)
|
|
243
256
|
|
|
244
|
-
logger.debug(
|
|
257
|
+
logger.debug(
|
|
258
|
+
f"bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}"
|
|
259
|
+
)
|
|
245
260
|
|
|
246
261
|
# Check for MTI
|
|
247
262
|
is_mti = False
|
|
@@ -257,7 +272,9 @@ class HookQuerySetMixin:
|
|
|
257
272
|
else:
|
|
258
273
|
logger.debug("bulk_update: hooks bypassed")
|
|
259
274
|
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
260
|
-
originals = [None] * len(
|
|
275
|
+
originals = [None] * len(
|
|
276
|
+
objs
|
|
277
|
+
) # Ensure originals is defined for after_update call
|
|
261
278
|
|
|
262
279
|
# Handle auto_now fields like Django's update_or_create does
|
|
263
280
|
fields_set = set(fields)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: django-bulk-hooks
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.233
|
|
4
4
|
Summary: Hook-style hooks for Django bulk operations like bulk_create and bulk_update.
|
|
5
|
+
Home-page: https://github.com/AugendLimited/django-bulk-hooks
|
|
5
6
|
License: MIT
|
|
6
7
|
Keywords: django,bulk,hooks
|
|
7
8
|
Author: Konrad Beck
|
|
@@ -13,7 +14,6 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
13
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
16
|
Requires-Dist: Django (>=4.0)
|
|
16
|
-
Project-URL: Homepage, https://github.com/AugendLimited/django-bulk-hooks
|
|
17
17
|
Project-URL: Repository, https://github.com/AugendLimited/django-bulk-hooks
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
|
|
@@ -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=7ug24q6M457TQwqpwqJHnxct1Hhhp9xnl8zR0cgIzWY,33237
|
|
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.233.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
15
|
+
django_bulk_hooks-0.1.233.dist-info/METADATA,sha256=yLbe8bU5rPxGaovxTa9_nKPujSRW-XoXjZ1Ok2GSidg,9049
|
|
16
|
+
django_bulk_hooks-0.1.233.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
17
|
+
django_bulk_hooks-0.1.233.dist-info/RECORD,,
|
|
File without changes
|