django-bulk-hooks 0.1.179__tar.gz → 0.1.181__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.

Files changed (17) hide show
  1. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/PKG-INFO +1 -1
  2. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/django_bulk_hooks/queryset.py +79 -31
  3. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/pyproject.toml +1 -1
  4. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/LICENSE +0 -0
  5. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/README.md +0 -0
  6. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/django_bulk_hooks/__init__.py +0 -0
  7. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/django_bulk_hooks/conditions.py +0 -0
  8. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/django_bulk_hooks/constants.py +0 -0
  9. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/django_bulk_hooks/context.py +0 -0
  10. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/django_bulk_hooks/decorators.py +0 -0
  11. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/django_bulk_hooks/engine.py +0 -0
  12. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/django_bulk_hooks/enums.py +0 -0
  13. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/django_bulk_hooks/handler.py +0 -0
  14. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/django_bulk_hooks/manager.py +0 -0
  15. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/django_bulk_hooks/models.py +0 -0
  16. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/django_bulk_hooks/priority.py +0 -0
  17. {django_bulk_hooks-0.1.179 → django_bulk_hooks-0.1.181}/django_bulk_hooks/registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-bulk-hooks
3
- Version: 0.1.179
3
+ Version: 0.1.181
4
4
  Summary: Hook-style hooks for Django bulk operations like bulk_create and bulk_update.
5
5
  License: MIT
6
6
  Keywords: django,bulk,hooks
@@ -17,29 +17,27 @@ from django_bulk_hooks.context import HookContext
17
17
 
18
18
 
19
19
  class HookQuerySet(models.QuerySet):
20
- CHUNK_SIZE = 200
21
-
22
20
  @transaction.atomic
23
21
  def delete(self):
24
22
  objs = list(self)
25
23
  if not objs:
26
24
  return 0
27
-
25
+
28
26
  model_cls = self.model
29
27
  ctx = HookContext(model_cls)
30
-
28
+
31
29
  # Run validation hooks first
32
30
  engine.run(model_cls, VALIDATE_DELETE, objs, ctx=ctx)
33
-
31
+
34
32
  # Then run business logic hooks
35
33
  engine.run(model_cls, BEFORE_DELETE, objs, ctx=ctx)
36
-
34
+
37
35
  # Use Django's standard delete() method
38
36
  result = super().delete()
39
-
37
+
40
38
  # Run AFTER_DELETE hooks
41
39
  engine.run(model_cls, AFTER_DELETE, objs, ctx=ctx)
42
-
40
+
43
41
  return result
44
42
 
45
43
  @transaction.atomic
@@ -109,10 +107,10 @@ class HookQuerySet(models.QuerySet):
109
107
  # trickier so it's not done yet.
110
108
  if batch_size is not None and batch_size <= 0:
111
109
  raise ValueError("Batch size must be a positive integer.")
112
-
110
+
113
111
  if not objs:
114
112
  return objs
115
-
113
+
116
114
  if any(not isinstance(obj, model_cls) for obj in objs):
117
115
  raise TypeError(
118
116
  f"bulk_create expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
@@ -177,7 +175,7 @@ class HookQuerySet(models.QuerySet):
177
175
  self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs
178
176
  ):
179
177
  """
180
- Bulk update objects in the database.
178
+ Bulk update objects in the database with MTI support.
181
179
  """
182
180
  model_cls = self.model
183
181
 
@@ -189,9 +187,15 @@ class HookQuerySet(models.QuerySet):
189
187
  f"bulk_update expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
190
188
  )
191
189
 
190
+ # Check for MTI
191
+ is_mti = False
192
+ for parent in model_cls._meta.all_parents:
193
+ if parent._meta.concrete_model is not model_cls._meta.concrete_model:
194
+ is_mti = True
195
+ break
196
+
192
197
  if not bypass_hooks:
193
- # Load originals for hook comparison and ensure they match the order of new instances
194
- # Use the base manager to avoid recursion
198
+ # Load originals for hook comparison
195
199
  original_map = {
196
200
  obj.pk: obj
197
201
  for obj in model_cls._base_manager.filter(
@@ -207,28 +211,31 @@ class HookQuerySet(models.QuerySet):
207
211
  engine.run(model_cls, VALIDATE_UPDATE, objs, originals, ctx=ctx)
208
212
 
209
213
  # Then run business logic hooks
210
- if not bypass_hooks:
211
- engine.run(model_cls, BEFORE_UPDATE, objs, originals, ctx=ctx)
214
+ engine.run(model_cls, BEFORE_UPDATE, objs, originals, ctx=ctx)
212
215
 
213
- # Automatically detect fields that were modified during BEFORE_UPDATE hooks
216
+ # Detect modified fields during hooks
214
217
  modified_fields = self._detect_modified_fields(objs, originals)
215
218
  if modified_fields:
216
- # Convert to set for efficient union operation
217
219
  fields_set = set(fields)
218
220
  fields_set.update(modified_fields)
219
221
  fields = list(fields_set)
220
222
 
221
- django_kwargs = {
222
- k: v
223
- for k, v in kwargs.items()
224
- if k not in ["bypass_hooks", "bypass_validation"]
225
- }
226
- super().bulk_update(objs, fields, **django_kwargs)
223
+ # Handle MTI models differently
224
+ if is_mti:
225
+ result = self._mti_bulk_update(objs, fields, **kwargs)
226
+ else:
227
+ # For single-table models, use Django's built-in bulk_update
228
+ django_kwargs = {
229
+ k: v
230
+ for k, v in kwargs.items()
231
+ if k not in ["bypass_hooks", "bypass_validation"]
232
+ }
233
+ result = super().bulk_update(objs, fields, **django_kwargs)
227
234
 
228
235
  if not bypass_hooks:
229
236
  engine.run(model_cls, AFTER_UPDATE, objs, originals, ctx=ctx)
230
237
 
231
- return objs
238
+ return result
232
239
 
233
240
  def _detect_modified_fields(self, new_instances, original_instances):
234
241
  """
@@ -302,13 +309,6 @@ class HookQuerySet(models.QuerySet):
302
309
  if inheritance_chain is None:
303
310
  inheritance_chain = self._get_inheritance_chain()
304
311
 
305
- print("inheritance_chain")
306
- print(inheritance_chain)
307
-
308
- for inheritance in inheritance_chain:
309
- print(inheritance)
310
-
311
-
312
312
  # Safety check to prevent infinite recursion
313
313
  if len(inheritance_chain) > 10: # Arbitrary limit to prevent infinite loops
314
314
  raise ValueError(
@@ -527,3 +527,51 @@ class HookQuerySet(models.QuerySet):
527
527
  field.pre_save(child_obj, add=True)
528
528
 
529
529
  return child_obj
530
+
531
+ def _mti_bulk_update(self, objs, fields, **kwargs):
532
+ """
533
+ Custom bulk update implementation for MTI models.
534
+ Updates each table in the inheritance chain efficiently using Django's batch_size.
535
+ """
536
+ model_cls = self.model
537
+ inheritance_chain = self._get_inheritance_chain()
538
+
539
+ # Group fields by model in the inheritance chain
540
+ field_groups = {}
541
+ for field_name in fields:
542
+ field = model_cls._meta.get_field(field_name)
543
+ # Find which model in the inheritance chain this field belongs to
544
+ for model in inheritance_chain:
545
+ if field in model._meta.local_fields:
546
+ if model not in field_groups:
547
+ field_groups[model] = []
548
+ field_groups[model].append(field_name)
549
+ break
550
+
551
+ # Update each table in the inheritance chain
552
+ total_updated = 0
553
+ for model, model_fields in field_groups.items():
554
+ if not model_fields:
555
+ continue
556
+
557
+ # Get objects that have this model's fields
558
+ model_objs = []
559
+ for obj in objs:
560
+ # Create a temporary object with just this model's fields
561
+ temp_obj = model()
562
+ temp_obj.pk = obj.pk # Set the primary key
563
+
564
+ # Copy only the fields for this model
565
+ for field_name in model_fields:
566
+ if hasattr(obj, field_name):
567
+ setattr(temp_obj, field_name, getattr(obj, field_name))
568
+
569
+ model_objs.append(temp_obj)
570
+
571
+ # Use Django's bulk_update for this model's table
572
+ # This will automatically use batch_size from kwargs
573
+ base_qs = model._base_manager.using(self.db)
574
+ updated_count = base_qs.bulk_update(model_objs, model_fields, **kwargs)
575
+ total_updated += updated_count
576
+
577
+ return total_updated
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-bulk-hooks"
3
- version = "0.1.179"
3
+ version = "0.1.181"
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"