django-bulk-hooks 0.1.236__py3-none-any.whl → 0.1.238__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 +64 -13
- {django_bulk_hooks-0.1.236.dist-info → django_bulk_hooks-0.1.238.dist-info}/METADATA +1 -1
- {django_bulk_hooks-0.1.236.dist-info → django_bulk_hooks-0.1.238.dist-info}/RECORD +5 -5
- {django_bulk_hooks-0.1.236.dist-info → django_bulk_hooks-0.1.238.dist-info}/LICENSE +0 -0
- {django_bulk_hooks-0.1.236.dist-info → django_bulk_hooks-0.1.238.dist-info}/WHEEL +0 -0
django_bulk_hooks/queryset.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
|
|
2
3
|
from django.db import models, transaction
|
|
3
4
|
from django.db.models import AutoField, Case, Field, Value, When
|
|
4
5
|
|
|
@@ -16,8 +17,8 @@ from django_bulk_hooks.constants import (
|
|
|
16
17
|
VALIDATE_DELETE,
|
|
17
18
|
VALIDATE_UPDATE,
|
|
18
19
|
)
|
|
19
|
-
from django_bulk_hooks.context import HookContext
|
|
20
20
|
from django_bulk_hooks.context import (
|
|
21
|
+
HookContext,
|
|
21
22
|
get_bulk_update_value_map,
|
|
22
23
|
set_bulk_update_value_map,
|
|
23
24
|
)
|
|
@@ -87,8 +88,9 @@ class HookQuerySetMixin:
|
|
|
87
88
|
|
|
88
89
|
# Check if we're in a bulk operation context to prevent double hook execution
|
|
89
90
|
from django_bulk_hooks.context import get_bypass_hooks
|
|
91
|
+
|
|
90
92
|
current_bypass_hooks = get_bypass_hooks()
|
|
91
|
-
|
|
93
|
+
|
|
92
94
|
# If we're in a bulk operation context, skip hooks to prevent double execution
|
|
93
95
|
if current_bypass_hooks:
|
|
94
96
|
logger.debug("update: skipping hooks (bulk context)")
|
|
@@ -101,6 +103,53 @@ class HookQuerySetMixin:
|
|
|
101
103
|
# Then run BEFORE_UPDATE hooks
|
|
102
104
|
engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
|
|
103
105
|
|
|
106
|
+
# Persist any additional field mutations made by BEFORE_UPDATE hooks.
|
|
107
|
+
# Build CASE statements per modified field not already present in kwargs.
|
|
108
|
+
modified_fields = self._detect_modified_fields(instances, originals)
|
|
109
|
+
extra_fields = [f for f in modified_fields if f not in kwargs]
|
|
110
|
+
if extra_fields:
|
|
111
|
+
case_statements = {}
|
|
112
|
+
for field_name in extra_fields:
|
|
113
|
+
try:
|
|
114
|
+
field_obj = model_cls._meta.get_field(field_name)
|
|
115
|
+
except Exception:
|
|
116
|
+
# Skip unknown fields
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
when_statements = []
|
|
120
|
+
for obj in instances:
|
|
121
|
+
obj_pk = getattr(obj, "pk", None)
|
|
122
|
+
if obj_pk is None:
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
# Determine value and output field
|
|
126
|
+
if getattr(field_obj, "is_relation", False):
|
|
127
|
+
# For FK fields, store the raw id and target field output type
|
|
128
|
+
value = getattr(obj, field_obj.attname, None)
|
|
129
|
+
output_field = field_obj.target_field
|
|
130
|
+
target_name = (
|
|
131
|
+
field_obj.attname
|
|
132
|
+
) # use column name (e.g., fk_id)
|
|
133
|
+
else:
|
|
134
|
+
value = getattr(obj, field_name)
|
|
135
|
+
output_field = field_obj
|
|
136
|
+
target_name = field_name
|
|
137
|
+
|
|
138
|
+
when_statements.append(
|
|
139
|
+
When(
|
|
140
|
+
pk=obj_pk, then=Value(value, output_field=output_field)
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if when_statements:
|
|
145
|
+
case_statements[target_name] = Case(
|
|
146
|
+
*when_statements, output_field=output_field
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Merge extra CASE updates into kwargs for DB update
|
|
150
|
+
if case_statements:
|
|
151
|
+
kwargs = {**kwargs, **case_statements}
|
|
152
|
+
|
|
104
153
|
# Use Django's built-in update logic directly
|
|
105
154
|
# Call the base QuerySet implementation to avoid recursion
|
|
106
155
|
update_count = super().update(**kwargs)
|
|
@@ -190,12 +239,12 @@ class HookQuerySetMixin:
|
|
|
190
239
|
|
|
191
240
|
# Fire hooks before DB ops
|
|
192
241
|
if not bypass_hooks:
|
|
193
|
-
ctx = HookContext(model_cls, bypass_hooks=False)
|
|
242
|
+
ctx = HookContext(model_cls, bypass_hooks=False) # Pass bypass_hooks
|
|
194
243
|
if not bypass_validation:
|
|
195
244
|
engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
|
|
196
245
|
engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
|
|
197
246
|
else:
|
|
198
|
-
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
247
|
+
ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
|
|
199
248
|
logger.debug("bulk_create bypassed hooks")
|
|
200
249
|
|
|
201
250
|
# For MTI models, we need to handle them specially
|
|
@@ -251,7 +300,9 @@ class HookQuerySetMixin:
|
|
|
251
300
|
f"bulk_update expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
|
|
252
301
|
)
|
|
253
302
|
|
|
254
|
-
logger.debug(
|
|
303
|
+
logger.debug(
|
|
304
|
+
f"bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}"
|
|
305
|
+
)
|
|
255
306
|
|
|
256
307
|
# Check for MTI
|
|
257
308
|
is_mti = False
|
|
@@ -267,7 +318,9 @@ class HookQuerySetMixin:
|
|
|
267
318
|
else:
|
|
268
319
|
logger.debug("bulk_update: hooks bypassed")
|
|
269
320
|
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
270
|
-
originals = [None] * len(
|
|
321
|
+
originals = [None] * len(
|
|
322
|
+
objs
|
|
323
|
+
) # Ensure originals is defined for after_update call
|
|
271
324
|
|
|
272
325
|
# Handle auto_now fields like Django's update_or_create does
|
|
273
326
|
fields_set = set(fields)
|
|
@@ -345,18 +398,16 @@ class HookQuerySetMixin:
|
|
|
345
398
|
if field.name == "id":
|
|
346
399
|
continue
|
|
347
400
|
|
|
348
|
-
new_value = getattr(new_instance, field.name)
|
|
349
|
-
original_value = getattr(original, field.name)
|
|
350
|
-
|
|
351
401
|
# Handle different field types appropriately
|
|
352
402
|
if field.is_relation:
|
|
353
|
-
#
|
|
354
|
-
new_pk =
|
|
355
|
-
original_pk =
|
|
403
|
+
# Compare by raw id values to catch cases where only <fk>_id was set
|
|
404
|
+
new_pk = getattr(new_instance, field.attname, None)
|
|
405
|
+
original_pk = getattr(original, field.attname, None)
|
|
356
406
|
if new_pk != original_pk:
|
|
357
407
|
modified_fields.add(field.name)
|
|
358
408
|
else:
|
|
359
|
-
|
|
409
|
+
new_value = getattr(new_instance, field.name)
|
|
410
|
+
original_value = getattr(original, field.name)
|
|
360
411
|
if new_value != original_value:
|
|
361
412
|
modified_fields.add(field.name)
|
|
362
413
|
|
|
@@ -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=OCKx6sXV1LG-aPOJ4gISjnn4zZzWE-R_NMGbzZF91HY,36156
|
|
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.238.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
15
|
+
django_bulk_hooks-0.1.238.dist-info/METADATA,sha256=DHtPWOMH6HjrqO3jx7Ez1JegBVENAxPJISuexntKE-U,9049
|
|
16
|
+
django_bulk_hooks-0.1.238.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
17
|
+
django_bulk_hooks-0.1.238.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|