django-bulk-hooks 0.1.103__tar.gz → 0.1.104__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.103 → django_bulk_hooks-0.1.104}/PKG-INFO +3 -3
  2. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/django_bulk_hooks/manager.py +121 -1
  3. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/pyproject.toml +1 -1
  4. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/LICENSE +0 -0
  5. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/README.md +0 -0
  6. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/django_bulk_hooks/__init__.py +0 -0
  7. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/django_bulk_hooks/conditions.py +0 -0
  8. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/django_bulk_hooks/constants.py +0 -0
  9. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/django_bulk_hooks/context.py +0 -0
  10. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/django_bulk_hooks/decorators.py +0 -0
  11. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/django_bulk_hooks/engine.py +0 -0
  12. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/django_bulk_hooks/enums.py +0 -0
  13. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/django_bulk_hooks/handler.py +0 -0
  14. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/django_bulk_hooks/models.py +0 -0
  15. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/django_bulk_hooks/priority.py +0 -0
  16. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/django_bulk_hooks/queryset.py +0 -0
  17. {django_bulk_hooks-0.1.103 → django_bulk_hooks-0.1.104}/django_bulk_hooks/registry.py +0 -0
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: django-bulk-hooks
3
- Version: 0.1.103
3
+ Version: 0.1.104
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
6
5
  License: MIT
7
6
  Keywords: django,bulk,hooks
8
7
  Author: Konrad Beck
@@ -14,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.11
14
13
  Classifier: Programming Language :: Python :: 3.12
15
14
  Classifier: Programming Language :: Python :: 3.13
16
15
  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
 
@@ -22,6 +22,114 @@ class BulkHookManager(models.Manager):
22
22
  def get_queryset(self):
23
23
  return HookQuerySet(self.model, using=self._db)
24
24
 
25
+ def _has_multi_table_inheritance(self, model_cls):
26
+ """
27
+ Check if this model uses multi-table inheritance.
28
+ """
29
+ return (
30
+ model_cls._meta.parents and
31
+ not all(parent._meta.abstract for parent in model_cls._meta.parents.values())
32
+ )
33
+
34
+ def _get_base_model(self, model_cls):
35
+ """
36
+ Get the base model (first non-abstract parent or self).
37
+ """
38
+ base_model = model_cls
39
+ while base_model._meta.parents:
40
+ parent = next(iter(base_model._meta.parents.values()))
41
+ if not parent._meta.abstract:
42
+ base_model = parent
43
+ else:
44
+ break
45
+ return base_model
46
+
47
+ def _extract_base_objects(self, objs, model_cls):
48
+ """
49
+ Extract base model objects from inherited objects.
50
+ """
51
+ base_model = self._get_base_model(model_cls)
52
+ base_objects = []
53
+
54
+ for obj in objs:
55
+ base_obj = base_model()
56
+ for field in base_model._meta.fields:
57
+ if field.name != 'id' and hasattr(obj, field.name):
58
+ setattr(base_obj, field.name, getattr(obj, field.name))
59
+ base_objects.append(base_obj)
60
+
61
+ return base_objects
62
+
63
+ def _extract_child_objects(self, objs, model_cls):
64
+ """
65
+ Extract child model objects from inherited objects.
66
+ """
67
+ child_objects = []
68
+
69
+ for obj in objs:
70
+ child_obj = model_cls()
71
+ child_obj.pk = obj.pk # Set the same PK as base
72
+
73
+ # Copy only fields specific to this model
74
+ for field in model_cls._meta.fields:
75
+ if (field.name != 'id' and
76
+ field.model == model_cls and
77
+ hasattr(obj, field.name)):
78
+ setattr(child_obj, field.name, getattr(obj, field.name))
79
+
80
+ child_objects.append(child_obj)
81
+
82
+ return child_objects
83
+
84
+ def _bulk_create_inherited(self, objs, **kwargs):
85
+ """
86
+ Handle bulk create for inherited models by handling each table separately.
87
+ """
88
+ if not objs:
89
+ return []
90
+
91
+ model_cls = self.model
92
+ result = []
93
+
94
+ # Group objects by their actual class
95
+ objects_by_class = {}
96
+ for obj in objs:
97
+ obj_class = obj.__class__
98
+ if obj_class not in objects_by_class:
99
+ objects_by_class[obj_class] = []
100
+ objects_by_class[obj_class].append(obj)
101
+
102
+ for obj_class, class_objects in objects_by_class.items():
103
+ # Check if this class has multi-table inheritance
104
+ parent_models = [p for p in obj_class._meta.get_parent_list()
105
+ if not p._meta.abstract]
106
+
107
+ if not parent_models:
108
+ # No inheritance, use standard bulk_create
109
+ chunk_result = super(models.Manager, self).bulk_create(class_objects, **kwargs)
110
+ result.extend(chunk_result)
111
+ continue
112
+
113
+ # Handle multi-table inheritance
114
+ # Step 1: Bulk create base objects without hooks
115
+ base_objects = self._extract_base_objects(class_objects, obj_class)
116
+ created_base = super(models.Manager, self).bulk_create(base_objects, **kwargs)
117
+
118
+ # Step 2: Update original objects with base IDs
119
+ for obj, base_obj in zip(class_objects, created_base):
120
+ obj.pk = base_obj.pk
121
+ obj._state.adding = False
122
+
123
+ # Step 3: Bulk create child objects without hooks
124
+ child_objects = self._extract_child_objects(class_objects, obj_class)
125
+ if child_objects:
126
+ # Use _base_manager to avoid recursion
127
+ obj_class._base_manager.bulk_create(child_objects, **kwargs)
128
+
129
+ result.extend(class_objects)
130
+
131
+ return result
132
+
25
133
  @transaction.atomic
26
134
  def bulk_update(
27
135
  self, objs, fields, bypass_hooks=False, bypass_validation=False, **kwargs
@@ -116,6 +224,9 @@ class BulkHookManager(models.Manager):
116
224
  f"bulk_create expected instances of {model_cls.__name__}, but got {set(type(obj).__name__ for obj in objs)}"
117
225
  )
118
226
 
227
+ # Check if this model uses multi-table inheritance
228
+ has_multi_table_inheritance = self._has_multi_table_inheritance(model_cls)
229
+
119
230
  result = []
120
231
 
121
232
  if not bypass_hooks:
@@ -128,9 +239,18 @@ class BulkHookManager(models.Manager):
128
239
  # Then run business logic hooks
129
240
  engine.run(model_cls, BEFORE_CREATE, objs, ctx=ctx)
130
241
 
242
+ # Perform bulk create in chunks
131
243
  for i in range(0, len(objs), self.CHUNK_SIZE):
132
244
  chunk = objs[i : i + self.CHUNK_SIZE]
133
- result.extend(super(models.Manager, self).bulk_create(chunk, **kwargs))
245
+
246
+ if has_multi_table_inheritance:
247
+ # Use our multi-table bulk create
248
+ created_chunk = self._bulk_create_inherited(chunk, **kwargs)
249
+ else:
250
+ # Use Django's standard bulk create
251
+ created_chunk = super(models.Manager, self).bulk_create(chunk, **kwargs)
252
+
253
+ result.extend(created_chunk)
134
254
 
135
255
  if not bypass_hooks:
136
256
  engine.run(model_cls, AFTER_CREATE, result, ctx=ctx)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "django-bulk-hooks"
3
- version = "0.1.103"
3
+ version = "0.1.104"
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"