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