django-bulk-hooks 0.1.236__py3-none-any.whl → 0.1.237__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.

@@ -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) # Pass bypass_hooks
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) # Pass bypass_hooks
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(f"bulk_update {model_cls.__name__} bypass_hooks={bypass_hooks} objs={len(objs)}")
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(objs) # Ensure originals is defined for after_update call
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
- # For foreign keys, compare the pk values
354
- new_pk = new_value.pk if new_value else None
355
- original_pk = original_value.pk if original_value else None
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
- # For regular fields, use direct comparison
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
  Metadata-Version: 2.1
2
2
  Name: django-bulk-hooks
3
- Version: 0.1.236
3
+ Version: 0.1.237
4
4
  Summary: Hook-style hooks for Django bulk operations like bulk_create and bulk_update.
5
5
  Home-page: https://github.com/AugendLimited/django-bulk-hooks
6
6
  License: MIT
@@ -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=wRT3g7shlHkpa0p0m5TjuyrS8SQKNmdhpcjdaSSMQHM,33937
12
+ django_bulk_hooks/queryset.py,sha256=5utEjCnrmJ7tbE2OrDgWf0LU6SCGYoJ1OecmaQW9HVM,35767
13
13
  django_bulk_hooks/registry.py,sha256=8UuhniiH5ChSeOKV1UUbqTEiIu25bZXvcHmkaRbxmME,1131
14
- django_bulk_hooks-0.1.236.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
- django_bulk_hooks-0.1.236.dist-info/METADATA,sha256=030mLHnwBTnsBWPmxLBc0KX9Qwrd2Yx0G3VDvZkVTps,9049
16
- django_bulk_hooks-0.1.236.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
17
- django_bulk_hooks-0.1.236.dist-info/RECORD,,
14
+ django_bulk_hooks-0.1.237.dist-info/LICENSE,sha256=dguKIcbDGeZD-vXWdLyErPUALYOvtX_fO4Zjhq481uk,1088
15
+ django_bulk_hooks-0.1.237.dist-info/METADATA,sha256=_jfrUKLLRuYMbka2ojsBYkINVC4kg8jbb5zVvwOhS8M,9049
16
+ django_bulk_hooks-0.1.237.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
17
+ django_bulk_hooks-0.1.237.dist-info/RECORD,,