django-bulk-hooks 0.1.220__py3-none-any.whl → 0.1.222__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/conditions.py +3 -6
- django_bulk_hooks/queryset.py +48 -37
- django_bulk_hooks/registry.py +3 -1
- {django_bulk_hooks-0.1.220.dist-info → django_bulk_hooks-0.1.222.dist-info}/METADATA +3 -3
- {django_bulk_hooks-0.1.220.dist-info → django_bulk_hooks-0.1.222.dist-info}/RECORD +7 -7
- {django_bulk_hooks-0.1.220.dist-info → django_bulk_hooks-0.1.222.dist-info}/WHEEL +1 -1
- {django_bulk_hooks-0.1.220.dist-info → django_bulk_hooks-0.1.222.dist-info}/LICENSE +0 -0
django_bulk_hooks/conditions.py
CHANGED
|
@@ -71,19 +71,16 @@ class HasChanged(HookCondition):
|
|
|
71
71
|
self.has_changed = has_changed
|
|
72
72
|
|
|
73
73
|
def check(self, instance, original_instance=None):
|
|
74
|
-
logger.debug(f"HasChanged.check called for field '{self.field}' on instance {getattr(instance, 'pk', 'No PK')}")
|
|
75
|
-
logger.debug(f"Original instance: {getattr(original_instance, 'pk', 'No PK') if original_instance else 'None'}")
|
|
76
|
-
|
|
77
74
|
if not original_instance:
|
|
78
|
-
logger.debug("No original instance, returning False")
|
|
79
75
|
return False
|
|
80
76
|
|
|
81
77
|
current = resolve_dotted_attr(instance, self.field)
|
|
82
78
|
previous = resolve_dotted_attr(original_instance, self.field)
|
|
83
79
|
|
|
84
|
-
# Add more detailed debugging
|
|
85
80
|
result = (current != previous) == self.has_changed
|
|
86
|
-
|
|
81
|
+
# Only log when there's an actual change to reduce noise
|
|
82
|
+
if result:
|
|
83
|
+
logger.debug(f"HasChanged {self.field} detected change on instance {getattr(instance, 'pk', 'No PK')}")
|
|
87
84
|
return result
|
|
88
85
|
|
|
89
86
|
|
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
|
|
|
@@ -70,44 +71,26 @@ class HookQuerySetMixin:
|
|
|
70
71
|
for value in kwargs.values()
|
|
71
72
|
)
|
|
72
73
|
|
|
73
|
-
# Apply field updates to instances
|
|
74
|
-
for obj in instances:
|
|
75
|
-
for field, value in kwargs.items():
|
|
76
|
-
setattr(obj, field, value)
|
|
77
|
-
|
|
78
74
|
# Check if we're in a bulk operation context to prevent double hook execution
|
|
79
75
|
from django_bulk_hooks.context import get_bypass_hooks
|
|
76
|
+
|
|
80
77
|
current_bypass_hooks = get_bypass_hooks()
|
|
81
|
-
|
|
82
|
-
# If we're in a bulk operation context, skip hooks to prevent double execution
|
|
83
|
-
if current_bypass_hooks:
|
|
84
|
-
logger.debug("update skipping hooks (bulk context)")
|
|
85
|
-
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
86
|
-
else:
|
|
87
|
-
logger.debug("update running hooks (standalone)")
|
|
88
|
-
ctx = HookContext(model_cls, bypass_hooks=False)
|
|
89
|
-
# Run validation hooks first
|
|
90
|
-
engine.run(model_cls, VALIDATE_UPDATE, instances, originals, ctx=ctx)
|
|
91
|
-
# Then run BEFORE_UPDATE hooks
|
|
92
|
-
engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
|
|
93
78
|
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
79
|
+
# If we used Subquery objects, we need to resolve them before hooks to avoid comparison errors
|
|
80
|
+
if has_subquery and not current_bypass_hooks:
|
|
81
|
+
# Execute the Django update first to compute subquery values
|
|
82
|
+
update_count = super().update(**kwargs)
|
|
97
83
|
|
|
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
|
|
84
|
+
# Refresh instances to get computed subquery values BEFORE running hooks
|
|
102
85
|
refreshed_instances = {
|
|
103
86
|
obj.pk: obj for obj in model_cls._base_manager.filter(pk__in=pks)
|
|
104
87
|
}
|
|
105
88
|
|
|
106
|
-
#
|
|
89
|
+
# Update instances in memory with computed values
|
|
107
90
|
for instance in instances:
|
|
108
91
|
if instance.pk in refreshed_instances:
|
|
109
92
|
refreshed_instance = refreshed_instances[instance.pk]
|
|
110
|
-
# Update all fields except primary key
|
|
93
|
+
# Update all fields except primary key with the computed values
|
|
111
94
|
for field in model_cls._meta.fields:
|
|
112
95
|
if field.name != "id":
|
|
113
96
|
setattr(
|
|
@@ -116,12 +99,36 @@ class HookQuerySetMixin:
|
|
|
116
99
|
getattr(refreshed_instance, field.name),
|
|
117
100
|
)
|
|
118
101
|
|
|
102
|
+
# Now run hooks with resolved subquery values
|
|
103
|
+
ctx = HookContext(model_cls, bypass_hooks=False)
|
|
104
|
+
# Run validation hooks first
|
|
105
|
+
engine.run(model_cls, VALIDATE_UPDATE, instances, originals, ctx=ctx)
|
|
106
|
+
# Then run BEFORE_UPDATE hooks
|
|
107
|
+
engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
|
|
108
|
+
else:
|
|
109
|
+
# Apply field updates to instances for non-subquery cases
|
|
110
|
+
for obj in instances:
|
|
111
|
+
for field, value in kwargs.items():
|
|
112
|
+
setattr(obj, field, value)
|
|
113
|
+
|
|
114
|
+
# If we're in a bulk operation context, skip hooks to prevent double execution
|
|
115
|
+
if current_bypass_hooks:
|
|
116
|
+
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
117
|
+
# For bulk operations without hooks, execute Django update if not done already
|
|
118
|
+
update_count = super().update(**kwargs)
|
|
119
|
+
else:
|
|
120
|
+
ctx = HookContext(model_cls, bypass_hooks=False)
|
|
121
|
+
# Run validation hooks first
|
|
122
|
+
engine.run(model_cls, VALIDATE_UPDATE, instances, originals, ctx=ctx)
|
|
123
|
+
# Then run BEFORE_UPDATE hooks
|
|
124
|
+
engine.run(model_cls, BEFORE_UPDATE, instances, originals, ctx=ctx)
|
|
125
|
+
|
|
126
|
+
# Execute Django update if not done already (for non-subquery cases)
|
|
127
|
+
update_count = super().update(**kwargs)
|
|
128
|
+
|
|
119
129
|
# Run AFTER_UPDATE hooks only for standalone updates
|
|
120
130
|
if not current_bypass_hooks:
|
|
121
|
-
logger.debug("update running AFTER_UPDATE")
|
|
122
131
|
engine.run(model_cls, AFTER_UPDATE, instances, originals, ctx=ctx)
|
|
123
|
-
else:
|
|
124
|
-
logger.debug("update skipping AFTER_UPDATE (bulk context)")
|
|
125
132
|
|
|
126
133
|
return update_count
|
|
127
134
|
|
|
@@ -180,12 +187,12 @@ class HookQuerySetMixin:
|
|
|
180
187
|
|
|
181
188
|
# Fire hooks before DB ops
|
|
182
189
|
if not bypass_hooks:
|
|
183
|
-
ctx = HookContext(model_cls, bypass_hooks=False)
|
|
190
|
+
ctx = HookContext(model_cls, bypass_hooks=False) # Pass bypass_hooks
|
|
184
191
|
if not bypass_validation:
|
|
185
192
|
engine.run(model_cls, VALIDATE_CREATE, objs, ctx=ctx)
|
|
186
193
|
engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
|
|
187
194
|
else:
|
|
188
|
-
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
195
|
+
ctx = HookContext(model_cls, bypass_hooks=True) # Pass bypass_hooks
|
|
189
196
|
logger.debug("bulk_create bypassed hooks")
|
|
190
197
|
|
|
191
198
|
# For MTI models, we need to handle them specially
|
|
@@ -241,7 +248,9 @@ class HookQuerySetMixin:
|
|
|
241
248
|
f"bulk_update expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
|
|
242
249
|
)
|
|
243
250
|
|
|
244
|
-
logger.debug(
|
|
251
|
+
logger.debug(
|
|
252
|
+
f"bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}"
|
|
253
|
+
)
|
|
245
254
|
|
|
246
255
|
# Check for MTI
|
|
247
256
|
is_mti = False
|
|
@@ -251,13 +260,15 @@ class HookQuerySetMixin:
|
|
|
251
260
|
break
|
|
252
261
|
|
|
253
262
|
if not bypass_hooks:
|
|
254
|
-
logger.debug("bulk_update
|
|
263
|
+
logger.debug("bulk_update: hooks will run in update()")
|
|
255
264
|
ctx = HookContext(model_cls, bypass_hooks=False)
|
|
256
265
|
originals = [None] * len(objs) # Placeholder for after_update call
|
|
257
266
|
else:
|
|
258
|
-
logger.debug("bulk_update
|
|
267
|
+
logger.debug("bulk_update: hooks bypassed")
|
|
259
268
|
ctx = HookContext(model_cls, bypass_hooks=True)
|
|
260
|
-
originals = [None] * len(
|
|
269
|
+
originals = [None] * len(
|
|
270
|
+
objs
|
|
271
|
+
) # Ensure originals is defined for after_update call
|
|
261
272
|
|
|
262
273
|
# Handle auto_now fields like Django's update_or_create does
|
|
263
274
|
fields_set = set(fields)
|
|
@@ -289,9 +300,9 @@ class HookQuerySetMixin:
|
|
|
289
300
|
# Note: We don't run AFTER_UPDATE hooks here to prevent double execution
|
|
290
301
|
# The update() method will handle all hook execution based on thread-local state
|
|
291
302
|
if not bypass_hooks:
|
|
292
|
-
logger.debug("bulk_update skipping AFTER_UPDATE (update() will handle)")
|
|
303
|
+
logger.debug("bulk_update: skipping AFTER_UPDATE (update() will handle)")
|
|
293
304
|
else:
|
|
294
|
-
logger.debug("bulk_update bypassed
|
|
305
|
+
logger.debug("bulk_update: hooks bypassed")
|
|
295
306
|
|
|
296
307
|
return result
|
|
297
308
|
|
django_bulk_hooks/registry.py
CHANGED
|
@@ -23,7 +23,9 @@ def register_hook(
|
|
|
23
23
|
def get_hooks(model, event):
|
|
24
24
|
key = (model, event)
|
|
25
25
|
hooks = _hooks.get(key, [])
|
|
26
|
-
|
|
26
|
+
# Only log when hooks are found or for specific events to reduce noise
|
|
27
|
+
if hooks or event in ['after_update', 'before_update', 'after_create', 'before_create']:
|
|
28
|
+
logger.debug(f"get_hooks {model.__name__}.{event} found {len(hooks)} hooks")
|
|
27
29
|
return hooks
|
|
28
30
|
|
|
29
31
|
|
|
@@ -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.222
|
|
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
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
django_bulk_hooks/__init__.py,sha256=uUgpnb9AWjIAcWNpCMqBcOewSnpJjJYH6cjPbQkzoNU,140
|
|
2
|
-
django_bulk_hooks/conditions.py,sha256=
|
|
2
|
+
django_bulk_hooks/conditions.py,sha256=V_f3Di2uCVUjoyfiU4BQCHmI4uUIRSRroApDcXlvnso,6349
|
|
3
3
|
django_bulk_hooks/constants.py,sha256=3x1H1fSUUNo0DZONN7GUVDuySZctTR-jtByBHmAIX5w,303
|
|
4
4
|
django_bulk_hooks/context.py,sha256=_NbGWTq9s66g0vbFIaqN4GlIHWQmFg3EQ44qY8YvvEg,1537
|
|
5
5
|
django_bulk_hooks/decorators.py,sha256=WD7Jn7QAvY8F4wOsYlIpjoM9-FdHXSKB7hH9ot-lkYQ,4896
|
|
@@ -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=
|
|
13
|
-
django_bulk_hooks/registry.py,sha256=
|
|
14
|
-
django_bulk_hooks-0.1.
|
|
15
|
-
django_bulk_hooks-0.1.
|
|
16
|
-
django_bulk_hooks-0.1.
|
|
17
|
-
django_bulk_hooks-0.1.
|
|
12
|
+
django_bulk_hooks/queryset.py,sha256=INcmUxugAxDY8M2Swvv4lItyniejT1CNUpJtEnFfoBw,33087
|
|
13
|
+
django_bulk_hooks/registry.py,sha256=8UuhniiH5ChSeOKV1UUbqTEiIu25bZXvcHmkaRbxmME,1131
|
|
14
|
+
django_bulk_hooks-0.1.222.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
|
|
15
|
+
django_bulk_hooks-0.1.222.dist-info/METADATA,sha256=QJazHusCLVY-qjWHDauyE9KUPPsnvGOLTd6ral0T9Wc,9049
|
|
16
|
+
django_bulk_hooks-0.1.222.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
17
|
+
django_bulk_hooks-0.1.222.dist-info/RECORD,,
|
|
File without changes
|