django-bulk-hooks 0.1.236__tar.gz → 0.1.237__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.
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/PKG-INFO +1 -1
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/django_bulk_hooks/queryset.py +55 -13
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/pyproject.toml +1 -1
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/LICENSE +0 -0
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/README.md +0 -0
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/django_bulk_hooks/__init__.py +0 -0
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/django_bulk_hooks/conditions.py +0 -0
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/django_bulk_hooks/constants.py +0 -0
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/django_bulk_hooks/context.py +0 -0
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/django_bulk_hooks/decorators.py +0 -0
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/django_bulk_hooks/engine.py +0 -0
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/django_bulk_hooks/enums.py +0 -0
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/django_bulk_hooks/handler.py +0 -0
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/django_bulk_hooks/manager.py +0 -0
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/django_bulk_hooks/models.py +0 -0
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/django_bulk_hooks/priority.py +0 -0
- {django_bulk_hooks-0.1.236 → django_bulk_hooks-0.1.237}/django_bulk_hooks/registry.py +0 -0
|
@@ -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,44 @@ 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
|
+
value = getattr(obj, field_name)
|
|
125
|
+
# Normalize relation values to instance or pk consistently
|
|
126
|
+
if getattr(field_obj, "is_relation", False):
|
|
127
|
+
# If a model instance is provided, use it; else use as-is
|
|
128
|
+
# Value() with output_field handles conversion
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
when_statements.append(
|
|
132
|
+
When(pk=obj_pk, then=Value(value, output_field=field_obj))
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if when_statements:
|
|
136
|
+
case_statements[field_name] = Case(
|
|
137
|
+
*when_statements, output_field=field_obj
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Merge extra CASE updates into kwargs for DB update
|
|
141
|
+
if case_statements:
|
|
142
|
+
kwargs = {**kwargs, **case_statements}
|
|
143
|
+
|
|
104
144
|
# Use Django's built-in update logic directly
|
|
105
145
|
# Call the base QuerySet implementation to avoid recursion
|
|
106
146
|
update_count = super().update(**kwargs)
|
|
@@ -190,12 +230,12 @@ class HookQuerySetMixin:
|
|
|
190
230
|
|
|
191
231
|
# Fire hooks before DB ops
|
|
192
232
|
if not bypass_hooks:
|
|
193
|
-
ctx = HookContext(model_cls, bypass_hooks=False)
|
|
233
|
+
ctx = HookContext(model_cls, bypass_hooks=False) # Pass bypass_hooks
|
|
194
234
|
if not bypass_validation:
|
|
195
235
|
engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
|
|
196
236
|
engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
|
|
197
237
|
else:
|
|
198
|
-
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
238
|
+
ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
|
|
199
239
|
logger.debug("bulk_create bypassed hooks")
|
|
200
240
|
|
|
201
241
|
# For MTI models, we need to handle them specially
|
|
@@ -251,7 +291,9 @@ class HookQuerySetMixin:
|
|
|
251
291
|
f"bulk_update expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
|
|
252
292
|
)
|
|
253
293
|
|
|
254
|
-
logger.debug(
|
|
294
|
+
logger.debug(
|
|
295
|
+
f"bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}"
|
|
296
|
+
)
|
|
255
297
|
|
|
256
298
|
# Check for MTI
|
|
257
299
|
is_mti = False
|
|
@@ -267,7 +309,9 @@ class HookQuerySetMixin:
|
|
|
267
309
|
else:
|
|
268
310
|
logger.debug("bulk_update: hooks bypassed")
|
|
269
311
|
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
270
|
-
originals = [None] * len(
|
|
312
|
+
originals = [None] * len(
|
|
313
|
+
objs
|
|
314
|
+
) # Ensure originals is defined for after_update call
|
|
271
315
|
|
|
272
316
|
# Handle auto_now fields like Django's update_or_create does
|
|
273
317
|
fields_set = set(fields)
|
|
@@ -345,18 +389,16 @@ class HookQuerySetMixin:
|
|
|
345
389
|
if field.name == "id":
|
|
346
390
|
continue
|
|
347
391
|
|
|
348
|
-
new_value = getattr(new_instance, field.name)
|
|
349
|
-
original_value = getattr(original, field.name)
|
|
350
|
-
|
|
351
392
|
# Handle different field types appropriately
|
|
352
393
|
if field.is_relation:
|
|
353
|
-
#
|
|
354
|
-
new_pk =
|
|
355
|
-
original_pk =
|
|
394
|
+
# Compare by raw id values to catch cases where only <fk>_id was set
|
|
395
|
+
new_pk = getattr(new_instance, field.attname, None)
|
|
396
|
+
original_pk = getattr(original, field.attname, None)
|
|
356
397
|
if new_pk != original_pk:
|
|
357
398
|
modified_fields.add(field.name)
|
|
358
399
|
else:
|
|
359
|
-
|
|
400
|
+
new_value = getattr(new_instance, field.name)
|
|
401
|
+
original_value = getattr(original, field.name)
|
|
360
402
|
if new_value != original_value:
|
|
361
403
|
modified_fields.add(field.name)
|
|
362
404
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "django-bulk-hooks"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.237"
|
|
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"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|